diff options
128 files changed, 3384 insertions, 1658 deletions
diff --git a/api/current.xml b/api/current.xml index 858b1e9..f45b68f 100644 --- a/api/current.xml +++ b/api/current.xml @@ -11018,6 +11018,56 @@ > </field> </class> +<class name="R.integer" + extends="java.lang.Object" + abstract="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +<constructor name="R.integer" + type="android.R.integer" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<field name="config_longAnimTime" + type="int" + transient="false" + volatile="false" + value="17694722" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="config_mediumAnimTime" + type="int" + transient="false" + volatile="false" + value="17694721" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="config_shortAnimTime" + type="int" + transient="false" + volatile="false" + value="17694720" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="R.layout" extends="java.lang.Object" abstract="false" @@ -28374,6 +28424,17 @@ visibility="public" > </field> +<field name="ACTION_SEARCH_LONG_PRESS" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.SEARCH_LONG_PRESS"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_SEND" type="java.lang.String" transient="false" @@ -66360,6 +66421,21 @@ <parameter name="c" type="android.hardware.Camera"> </parameter> </method> +<method name="setMaxDuration" + return="void" + abstract="false" + native="true" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="max_duration_ms" type="int"> +</parameter> +<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException"> +</exception> +</method> <method name="setOnErrorListener" return="void" abstract="false" @@ -66373,6 +66449,19 @@ <parameter name="l" type="android.media.MediaRecorder.OnErrorListener"> </parameter> </method> +<method name="setOnInfoListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.media.MediaRecorder.OnInfoListener"> +</parameter> +</method> <method name="setOutputFile" return="void" abstract="false" @@ -66530,6 +66619,28 @@ visibility="public" > </field> +<field name="MEDIA_RECORDER_INFO_MAX_DURATION_REACHED" + type="int" + transient="false" + volatile="false" + value="800" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="MEDIA_RECORDER_INFO_UNKNOWN" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="MediaRecorder.AudioEncoder" extends="java.lang.Object" @@ -66618,6 +66729,31 @@ </parameter> </method> </interface> +<interface name="MediaRecorder.OnInfoListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onInfo" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="mr" type="android.media.MediaRecorder"> +</parameter> +<parameter name="what" type="int"> +</parameter> +<parameter name="extra" type="int"> +</parameter> +</method> +</interface> <class name="MediaRecorder.OutputFormat" extends="java.lang.Object" abstract="false" @@ -84442,6 +84578,17 @@ visibility="public" > </field> +<field name="EXTRA_APPLICATION_ID" + type="java.lang.String" + transient="false" + volatile="false" + value=""com.android.browser.application_id"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="HISTORY_PROJECTION" type="java.lang.String[]" transient="false" diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c index 5ef7499..eabf98e 100644 --- a/cmds/dumpstate/dumpstate.c +++ b/cmds/dumpstate/dumpstate.c @@ -130,7 +130,7 @@ static void dumpstate(int full) { /* the full dumpsys is starting to take a long time, so we need to increase its timeout. we really need to do the timeouts in dumpsys itself... */ - EXEC_TIMEOUT("dumpsys", 40); + EXEC_TIMEOUT("dumpsys", 60); } } diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 33127fb..6862e5a 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -19,8 +19,8 @@ #include <time.h> -// Commands time out after 15 seconds -#define TIMEOUT 15 +// Commands time out after 60 seconds +#define TIMEOUT 60 #define PRINT(s) printf("%s\n", s) diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 15e0a4d..64288d2 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -126,14 +126,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // support for AutoCompleteTextView suggestions display private SuggestionsAdapter mSuggestionsAdapter; - private Handler mHandler = new Handler(); - private Runnable mInstallSuggestionAdapter = new Runnable() { - public void run() { - if (mSearchTextField != null) { - mSearchTextField.setAdapter(mSuggestionsAdapter); - } - } - }; /** * Constructor - fires it up and makes it look like the search UI. @@ -261,8 +253,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchTextField.setAdapter(mSuggestionsAdapter); mSearchTextField.setText(initialQuery); } else { - mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable, - mHandler, mInstallSuggestionAdapter); + mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable, + mSearchTextField); mSearchTextField.setAdapter(mSuggestionsAdapter); // finally, load the user's initial text (which may trigger suggestions) @@ -1305,15 +1297,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // These private variables are shared by the filter thread and must be protected private WeakReference<Cursor> mRecentCursor = new WeakReference<Cursor>(null); private boolean mNonUserQuery = false; - private Handler mHandler; - private Runnable mInstallSuggestionAdapter; + private AutoCompleteTextView mParentView; public SuggestionsAdapter(Context context, SearchableInfo searchable, - Handler handler, Runnable installSuggestionAdapter) { + AutoCompleteTextView actv) { super(context, -1, null, null, null); mSearchable = searchable; - mHandler = handler; - mInstallSuggestionAdapter = installSuggestionAdapter; + mParentView = actv; // set up provider resources (gives us icons, etc.) Context activityContext = mSearchable.getActivityContext(mContext); @@ -1426,13 +1416,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS to = ONE_LINE_TO; } } + // Force the underlying ListView to discard and reload all layouts + // (Note, this should be optimized for cases where layout/cursor remain same) + mParentView.resetListAndClearViews(); // Now actually set up the cursor, columns, and the list view changeCursorAndColumns(c, from, to); setViewResource(layout); - // Force the underlying ListView to discard and reload all layouts - // (Note, this could be optimized for cases where layout/cursor remain same) - mHandler.post(mInstallSuggestionAdapter); - } else { // Provide some help for developers instead of just silently discarding Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns."); diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index e23545b..1dbe0cc 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -67,6 +67,11 @@ public class BluetoothHeadset { /** A headset is currently connected */ public static final int STATE_CONNECTED = 2; + /** A SCO audio channel is not established */ + public static final int AUDIO_STATE_DISCONNECTED = 0; + /** A SCO audio channel is established */ + public static final int AUDIO_STATE_CONNECTED = 1; + public static final int RESULT_FAILURE = 0; public static final int RESULT_SUCCESS = 1; /** Connection canceled before completetion. */ diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java index b66b06e..9273d0d 100644 --- a/core/java/android/bluetooth/BluetoothIntent.java +++ b/core/java/android/bluetooth/BluetoothIntent.java @@ -45,6 +45,8 @@ public interface BluetoothIntent { "android.bluetooth.intent.HEADSET_STATE"; public static final String HEADSET_PREVIOUS_STATE = "android.bluetooth.intent.HEADSET_PREVIOUS_STATE"; + public static final String HEADSET_AUDIO_STATE = + "android.bluetooth.intent.HEADSET_AUDIO_STATE"; public static final String BOND_STATE = "android.bluetooth.intent.BOND_STATE"; public static final String BOND_PREVIOUS_STATE = @@ -122,7 +124,18 @@ public interface BluetoothIntent { public static final String BOND_STATE_CHANGED_ACTION = "android.bluetooth.intent.action.BOND_STATE_CHANGED_ACTION"; + /** + * TODO(API release): Move into BluetoothHeadset + */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String HEADSET_STATE_CHANGED_ACTION = "android.bluetooth.intent.action.HEADSET_STATE_CHANGED"; + + /** + * TODO(API release): Consider incorporating as new state in + * HEADSET_STATE_CHANGED + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String HEADSET_AUDIO_STATE_CHANGED_ACTION = + "android.bluetooth.intent.action.HEADSET_ADUIO_STATE_CHANGED"; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index bb80e10..90ff78a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1026,6 +1026,15 @@ public class Intent implements Parcelable { */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND"; + + /** + * Activity Action: Start action associated with long pressing on the + * search key. + * <p>Input: Nothing. + * <p>Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS"; // --------------------------------------------------------------------- // --------------------------------------------------------------------- diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index f58b7ef..7a63c0c 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -16,34 +16,33 @@ package android.inputmethodservice; -import com.android.internal.R; - import android.content.Context; -import android.content.SharedPreferences; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.Paint.Align; +import android.graphics.Region.Op; import android.graphics.drawable.Drawable; import android.inputmethodservice.Keyboard.Key; import android.os.Handler; import android.os.Message; -import android.os.Vibrator; -import android.preference.PreferenceManager; +import android.os.SystemClock; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewGroup.LayoutParams; -import android.widget.Button; import android.widget.PopupWindow; import android.widget.TextView; +import com.android.internal.R; + import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -221,6 +220,15 @@ public class KeyboardView extends View implements View.OnClickListener { private static final int MULTITAP_INTERVAL = 800; // milliseconds private StringBuilder mPreviewLabel = new StringBuilder(1); + /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ + private boolean mDrawPending; + /** The dirty region in the keyboard bitmap */ + private Rect mDirtyRect = new Rect(); + /** The keyboard bitmap for faster updates */ + private Bitmap mBuffer; + /** The canvas for the above mutable keyboard bitmap */ + private Canvas mCanvas; + Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -396,7 +404,10 @@ public class KeyboardView extends View implements View.OnClickListener { List<Key> keys = mKeyboard.getKeys(); mKeys = keys.toArray(new Key[keys.size()]); requestLayout(); - invalidate(); + // Release buffer, just in case the new keyboard has a different size. + // It will be reallocated on the next draw. + mBuffer = null; + invalidateAll(); computeProximityThreshold(keyboard); mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views } @@ -420,7 +431,7 @@ public class KeyboardView extends View implements View.OnClickListener { if (mKeyboard != null) { if (mKeyboard.setShifted(shifted)) { // The whole keyboard probably needs to be redrawn - invalidate(); + invalidateAll(); return true; } } @@ -545,8 +556,30 @@ public class KeyboardView extends View implements View.OnClickListener { } @Override + public void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + // Release the buffer, if any and it will be reallocated on the next draw + mBuffer = null; + } + + @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); + if (mDrawPending || mBuffer == null) { + onBufferDraw(); + } + canvas.drawBitmap(mBuffer, 0, 0, null); + } + + private void onBufferDraw() { + if (mBuffer == null) { + mBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBuffer); + invalidateAll(); + } + final Canvas canvas = mCanvas; + canvas.clipRect(mDirtyRect, Op.REPLACE); + if (mKeyboard == null) return; final Paint paint = mPaint; @@ -557,24 +590,20 @@ public class KeyboardView extends View implements View.OnClickListener { final int kbdPaddingTop = mPaddingTop; final Key[] keys = mKeys; final Key invalidKey = mInvalidatedKey; - //canvas.translate(0, mKeyboardPaddingTop); + paint.setAlpha(255); paint.setColor(mKeyTextColor); boolean drawSingleKey = false; if (invalidKey != null && canvas.getClipBounds(clipRegion)) { -// System.out.println("Key bounds = " + (invalidKey.x + mPaddingLeft) + "," -// + (invalidKey.y + mPaddingTop) + "," -// + (invalidKey.x + invalidKey.width + mPaddingLeft) + "," -// + (invalidKey.y + invalidKey.height + mPaddingTop)); -// System.out.println("Clip bounds =" + clipRegion.toShortString()); - // Is clipRegion completely contained within the invalidated key? - if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && - invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && - invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && - invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { - drawSingleKey = true; - } + // Is clipRegion completely contained within the invalidated key? + if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && + invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && + invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && + invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { + drawSingleKey = true; + } } + canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); final int keyCount = keys.length; for (int i = 0; i < keyCount; i++) { final Key key = keys[i]; @@ -645,6 +674,9 @@ public class KeyboardView extends View implements View.OnClickListener { paint.setColor(0xFF00FF00); canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint); } + + mDrawPending = false; + mDirtyRect.setEmpty(); } private int getKeyIndices(int x, int y, int[] allKeys) { @@ -844,12 +876,21 @@ public class KeyboardView extends View implements View.OnClickListener { mPreviewText.setVisibility(VISIBLE); } + private void invalidateAll() { + mDirtyRect.union(0, 0, getWidth(), getHeight()); + mDrawPending = true; + invalidate(); + } + private void invalidateKey(int keyIndex) { if (keyIndex < 0 || keyIndex >= mKeys.length) { return; } final Key key = mKeys[keyIndex]; mInvalidatedKey = key; + mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop, + key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop); + onBufferDraw(); invalidate(key.x + mPaddingLeft, key.y + mPaddingTop, key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop); } @@ -952,7 +993,7 @@ public class KeyboardView extends View implements View.OnClickListener { mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y); mMiniKeyboardOnScreen = true; //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me)); - invalidate(); + invalidateAll(); return true; } return false; @@ -1066,11 +1107,11 @@ public class KeyboardView extends View implements View.OnClickListener { } showPreview(NOT_A_KEY); Arrays.fill(mKeyIndices, NOT_A_KEY); - invalidateKey(keyIndex); // If we're not on a repeating key (which sends on a DOWN event) if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) { detectAndSendKey(touchX, touchY, eventTime); } + invalidateKey(keyIndex); mRepeatKeyIndex = NOT_A_KEY; break; } @@ -1110,7 +1151,8 @@ public class KeyboardView extends View implements View.OnClickListener { mHandler.removeMessages(MSG_SHOW_PREVIEW); dismissPopupKeyboard(); - + mBuffer = null; + mCanvas = null; mMiniKeyboardCache.clear(); } @@ -1124,10 +1166,10 @@ public class KeyboardView extends View implements View.OnClickListener { if (mPopupKeyboard.isShowing()) { mPopupKeyboard.dismiss(); mMiniKeyboardOnScreen = false; - invalidate(); + invalidateAll(); } } - + public boolean handleBack() { if (mPopupKeyboard.isShowing()) { dismissPopupKeyboard(); diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl index 88dae85..96d44b6 100644 --- a/core/java/android/os/IMountService.aidl +++ b/core/java/android/os/IMountService.aidl @@ -50,7 +50,7 @@ interface IMountService void unmountMedia(String mountPoint); /** - * Format external storage given a mount point + * Format external storage given a mount point. */ void formatMedia(String mountPoint); @@ -63,4 +63,16 @@ interface IMountService * Sets whether or not media notification sounds are played. */ void setPlayNotificationSounds(boolean value); + + /** + * Returns true if USB Mass Storage is automatically started + * when a UMS host is detected. + */ + boolean getAutoStartUms(); + + /** + * Sets whether or not USB Mass Storage is automatically started + * when a UMS host is detected. + */ + void setAutoStartUms(boolean value); } diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index 6e215dc..20702a1 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -194,13 +194,6 @@ public class VolumePreference extends SeekBarPreference implements } private void sample() { - - // Only play a preview sample when controlling the ringer stream - if (mStreamType != AudioManager.STREAM_RING - && mStreamType != AudioManager.STREAM_NOTIFICATION) { - return; - } - onSampleStarting(this); mRingtone.play(); } diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java index 76aa51d..c597b3c 100644 --- a/core/java/android/provider/Browser.java +++ b/core/java/android/provider/Browser.java @@ -43,6 +43,18 @@ public class Browser { */ public static final String INITIAL_ZOOM_LEVEL = "browser.initialZoomLevel"; + /** + * The name of the extra data when starting the Browser from another + * application. + * <p> + * The value is a unique identification string that will be used to + * indentify the calling application. The Browser will attempt to reuse the + * same window each time the application launches the Browser with the same + * identifier. + */ + public static final String EXTRA_APPLICATION_ID = + "com.android.browser.application_id"; + /* if you change column order you must also change indices below */ public static final String[] HISTORY_PROJECTION = new String[] { diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index d0bd2a5..2aa77ea 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -182,7 +182,7 @@ public class Contacts { * <p>Type: TEXT</P> */ public static final String PHONETIC_NAME = "phonetic_name"; - + /** * The display name. If name is not null name, else if number is not null number, * else if email is not null email. @@ -191,6 +191,14 @@ public class Contacts { public static final String DISPLAY_NAME = "display_name"; /** + * The field for sorting list phonetically. The content of this field + * may not be human readable but phonetically sortable. + * <P>Type: TEXT</p> + * @hide Used only in Contacts application for now. + */ + public static final String SORT_STRING = "sort_string"; + + /** * Notes about the person. * <P>Type: TEXT</P> */ @@ -231,7 +239,7 @@ public class Contacts { * The server version of the photo * <P>Type: TEXT (the version number portion of the photo URI)</P> */ - public static final String PHOTO_VERSION = "photo_version"; + public static final String PHOTO_VERSION = "photo_version"; } /** diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index e271909..9e9ba62 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -81,10 +81,13 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { */ public synchronized void init() { initializeNativeDataNative(); - mIsEnabled = (isEnabledNative() == 1); - if (mIsEnabled) { - mBondState.loadBondState(); + + if (isEnabledNative() == 1) { + Log.w(TAG, "Bluetooth daemons already running - runtime restart? "); + disableNative(); } + + mIsEnabled = false; mIsDiscovering = false; mEventLoop = new BluetoothEventLoop(mContext, this); registerForAirplaneMode(); diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java index efb88a0..29c0c76 100644 --- a/core/java/android/text/style/ImageSpan.java +++ b/core/java/android/text/style/ImageSpan.java @@ -88,6 +88,7 @@ public class ImageSpan extends DynamicDrawableSpan { super(verticalAlignment); mContext = context; mContentUri = uri; + mSource = uri.toString(); } public ImageSpan(Context context, int resourceId) { @@ -117,6 +118,8 @@ public class ImageSpan extends DynamicDrawableSpan { mContentUri); bitmap = BitmapFactory.decodeStream(is); drawable = new BitmapDrawable(bitmap); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight()); is.close(); } catch (Exception e) { Log.e("sms", "Failed to loaded content " + mContentUri, e); diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java index f458611..d29bfb6 100644 --- a/core/java/android/text/style/URLSpan.java +++ b/core/java/android/text/style/URLSpan.java @@ -16,9 +16,11 @@ package android.text.style; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Parcel; +import android.provider.Browser; import android.text.ParcelableSpan; import android.text.TextUtils; import android.view.View; @@ -54,8 +56,9 @@ public class URLSpan extends ClickableSpan implements ParcelableSpan { @Override public void onClick(View widget) { Uri uri = Uri.parse(getURL()); + Context context = widget.getContext(); Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - widget.getContext().startActivity(intent); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + context.startActivity(intent); } } diff --git a/core/java/android/text/util/Regex.java b/core/java/android/text/util/Regex.java index 4c128ad..a349b82 100644 --- a/core/java/android/text/util/Regex.java +++ b/core/java/android/text/util/Regex.java @@ -66,9 +66,9 @@ public class Regex { public static final Pattern WEB_URL_PATTERN = Pattern.compile( "((?:(http|https|Http|Https):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" - + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2}))+(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" - + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2}))+)?\\@)?)?" - + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.)+" // named host + + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?" + + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}\\.)+" // named host + "(?:" // plus top level domain + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])" + "|(?:biz|b[abdefghijmnorstvwyz])" @@ -122,12 +122,12 @@ public class Regex { public static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile( - "[a-zA-Z0-9\\+\\.\\_\\%\\-]+" + + "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" + "\\@" + - "[a-zA-Z0-9][a-zA-Z0-9\\-]*" + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + "(" + "\\." + - "[a-zA-Z0-9][a-zA-Z0-9\\-]*" + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + ")+" ); diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 0a043bd..6ea7a82 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -663,7 +663,7 @@ public class ViewDebug { public Object[] pre() { final DisplayMetrics metrics = view.getResources().getDisplayMetrics(); final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels, - metrics.heightPixels, Bitmap.Config.ARGB_8888); + metrics.heightPixels, Bitmap.Config.RGB_565); final Canvas canvas = new Canvas(bitmap); return new Object[] { bitmap, canvas }; } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 9b13d38..dd2b154 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -219,7 +219,7 @@ public final class ViewRoot extends Handler implements ViewParent, mVisRect = new Rect(); mVisPoint = new Point(); mWinFrame = new Rect(); - mWindow = new W(this); + mWindow = new W(this, context); mInputMethodCallback = new InputMethodCallback(this); mViewVisibility = View.GONE; mTransparentRegion = new Region(); @@ -2453,11 +2453,71 @@ public final class ViewRoot extends Handler implements ViewParent, } } + static class EventCompletion extends Handler { + final IWindow mWindow; + final KeyEvent mKeyEvent; + final boolean mIsPointer; + final MotionEvent mMotionEvent; + + EventCompletion(Looper looper, IWindow window, KeyEvent key, + boolean isPointer, MotionEvent motion) { + super(looper); + mWindow = window; + mKeyEvent = key; + mIsPointer = isPointer; + mMotionEvent = motion; + sendEmptyMessage(0); + } + + @Override + public void handleMessage(Message msg) { + if (mKeyEvent != null) { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } else if (mIsPointer) { + boolean didFinish; + MotionEvent event = mMotionEvent; + if (event == null) { + try { + event = sWindowSession.getPendingPointerMove(mWindow); + } catch (RemoteException e) { + } + didFinish = true; + } else { + didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE; + } + if (!didFinish) { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + } else { + MotionEvent event = mMotionEvent; + if (event == null) { + try { + event = sWindowSession.getPendingTrackballMove(mWindow); + } catch (RemoteException e) { + } + } else { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + } + } + } + static class W extends IWindow.Stub { - private WeakReference<ViewRoot> mViewRoot; + private final WeakReference<ViewRoot> mViewRoot; + private final Looper mMainLooper; - public W(ViewRoot viewRoot) { + public W(ViewRoot viewRoot, Context context) { mViewRoot = new WeakReference<ViewRoot>(viewRoot); + mMainLooper = context.getMainLooper(); } public void resized(int w, int h, Rect coveredInsets, @@ -2475,6 +2535,7 @@ public final class ViewRoot extends Handler implements ViewParent, viewRoot.dispatchKey(event); } else { Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!"); + new EventCompletion(mMainLooper, this, event, false, null); } } @@ -2482,6 +2543,8 @@ public final class ViewRoot extends Handler implements ViewParent, final ViewRoot viewRoot = mViewRoot.get(); if (viewRoot != null) { viewRoot.dispatchPointer(event, eventTime); + } else { + new EventCompletion(mMainLooper, this, null, true, event); } } @@ -2489,6 +2552,8 @@ public final class ViewRoot extends Handler implements ViewParent, final ViewRoot viewRoot = mViewRoot.get(); if (viewRoot != null) { viewRoot.dispatchTrackball(event, eventTime); + } else { + new EventCompletion(mMainLooper, this, null, false, event); } } diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java index 5877932..f1f5f70 100755 --- a/core/java/android/view/WindowOrientationListener.java +++ b/core/java/android/view/WindowOrientationListener.java @@ -34,24 +34,12 @@ public abstract class WindowOrientationListener { private static final String TAG = "WindowOrientationListener"; private static final boolean DEBUG = false; private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; - private int mOrientation = ORIENTATION_UNKNOWN; private SensorManager mSensorManager; private boolean mEnabled = false; private int mRate; private Sensor mSensor; private SensorEventListener mSensorEventListener; - - /** - * Returned from onOrientationChanged when the device orientation cannot be determined - * (typically when the device is in a close to flat position). - * - * @see #onOrientationChanged - */ - public static final int ORIENTATION_UNKNOWN = -1; - /* - * Returned when the device is almost lying flat on a surface - */ - public static final int ORIENTATION_FLAT = -2; + private int mSensorRotation = -1; /** * Creates a new WindowOrientationListener. @@ -116,24 +104,47 @@ public abstract class WindowOrientationListener { private static final int _DATA_X = 0; private static final int _DATA_Y = 1; private static final int _DATA_Z = 2; + // Angle around x-axis thats considered almost perfect vertical to hold + // the device + private static final int PIVOT = 30; + // Angle around x-asis that's considered almost too vertical. Beyond + // this angle will not result in any orientation changes. f phone faces uses, + // the device is leaning backward. + private static final int PIVOT_UPPER = 65; + // Angle about x-axis that's considered negative vertical. Beyond this + // angle will not result in any orientation changes. If phone faces uses, + // the device is leaning forward. + private static final int PIVOT_LOWER = 0; + // Upper threshold limit for switching from portrait to landscape + private static final int PL_UPPER = 280; + // Lower threshold limit for switching from landscape to portrait + private static final int LP_LOWER = 320; + // Lower threshold limt for switching from portrait to landscape + private static final int PL_LOWER = 240; + // Upper threshold limit for switching from landscape to portrait + private static final int LP_UPPER = 360; + + // Internal value used for calculating linear variant + private static final float PL_LINEAR_FACTOR = + ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT_UPPER-PIVOT_LOWER)); + // Internal value used for calculating linear variant + private static final float LP_LINEAR_FACTOR = + ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT_UPPER-PIVOT_LOWER)); public void onSensorChanged(SensorEvent event) { float[] values = event.values; - int orientation = ORIENTATION_UNKNOWN; float X = values[_DATA_X]; float Y = values[_DATA_Y]; float Z = values[_DATA_Z]; float OneEightyOverPi = 57.29577957855f; float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z); float zyangle = Math.abs((float)Math.asin(Z/gravity)*OneEightyOverPi); - // The device is considered flat if the angle is more than 75 - // if the angle is less than 40, its considered too flat to switch - // orientation. if the angle is between 40 - 75, the orientation is unknown - if (zyangle < 40) { + int rotation = mSensorRotation; + if ((zyangle <= PIVOT_UPPER) && (zyangle >= PIVOT_LOWER)) { // Check orientation only if the phone is flat enough // Don't trust the angle if the magnitude is small compared to the y value float angle = (float)Math.atan2(Y, -X) * OneEightyOverPi; - orientation = 90 - (int)Math.round(angle); + int orientation = 90 - (int)Math.round(angle); // normalize to 0 - 359 range while (orientation >= 360) { orientation -= 360; @@ -141,13 +152,24 @@ public abstract class WindowOrientationListener { while (orientation < 0) { orientation += 360; } - } else if (zyangle >= 75){ - orientation = ORIENTATION_FLAT; + + float delta = (float)Math.abs(zyangle - PIVOT); + if (((orientation >= 0) && (orientation <= LP_UPPER)) || + (orientation >= PL_LOWER)) { + float threshold; + if (mSensorRotation == Surface.ROTATION_90) { + threshold = LP_LOWER + (LP_LINEAR_FACTOR * delta) ; + } else { + threshold = PL_UPPER - (PL_LINEAR_FACTOR * delta); + } + rotation = (orientation >= PL_LOWER && + orientation <= threshold) ? Surface.ROTATION_90 : Surface.ROTATION_0; + } + } - - if (orientation != mOrientation) { - mOrientation = orientation; - onOrientationChanged(orientation); + if (rotation != mSensorRotation) { + mSensorRotation = rotation; + onOrientationChanged(mSensorRotation); } } @@ -164,17 +186,11 @@ public abstract class WindowOrientationListener { } /** - * Called when the orientation of the device has changed. - * orientation parameter is in degrees, ranging from 0 to 359. - * orientation is 0 degrees when the device is oriented in its natural position, - * 90 degrees when its left side is at the top, 180 degrees when it is upside down, - * and 270 degrees when its right side is to the top. - * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat - * and the orientation cannot be determined. - * - * @param orientation The new orientation of the device. + * Called when the rotation view of the device has changed. + * Can be either Surface.ROTATION_90 or Surface.ROTATION_0. + * @param rotation The new orientation of the device. * * @see #ORIENTATION_UNKNOWN */ - abstract public void onOrientationChanged(int orientation); + abstract public void onOrientationChanged(int rotation); } diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index d12940d..dcf68cd 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -52,6 +52,7 @@ public final class CacheManager { private static final String NO_STORE = "no-store"; private static final String NO_CACHE = "no-cache"; + private static final String PRIVATE = "private"; private static final String MAX_AGE = "max-age"; private static long CACHE_THRESHOLD = 6 * 1024 * 1024; @@ -612,7 +613,7 @@ public final class CacheManager { // must be re-validated on every load. It does not mean that // the content can not be cached. set to expire 0 means it // can only be used in CACHE_MODE_CACHE_ONLY case - if (NO_CACHE.equals(controls[i])) { + if (NO_CACHE.equals(controls[i]) || PRIVATE.equals(controls[i])) { ret.expires = 0; } else if (controls[i].startsWith(MAX_AGE)) { int separator = controls[i].indexOf('='); diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index 84aeb83..0f9f29c 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -29,6 +29,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.provider.Browser; import android.util.Config; import android.util.Log; import android.view.KeyEvent; @@ -175,6 +176,11 @@ class CallbackProxy extends Handler { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(overrideUrl)); intent.addCategory(Intent.CATEGORY_BROWSABLE); + // If another application is running a WebView and launches the + // Browser through this Intent, we want to reuse the same window if + // possible. + intent.putExtra(Browser.EXTRA_APPLICATION_ID, + mContext.getPackageName()); try { mContext.startActivity(intent); override = true; diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 07c1a5d..d90a2fd 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -743,11 +743,16 @@ public final class CookieManager { * Note: in the case of "foo=bluh, bar=bluh;path=/", we interpret * it as one cookie instead of two cookies. */ + int semicolonIndex = cookieString.indexOf(SEMICOLON, index); int equalIndex = cookieString.indexOf(EQUAL, index); if (equalIndex == -1) { // bad format, force return break; } + if (semicolonIndex > -1 && semicolonIndex < equalIndex) { + // empty cookie, like "; path=/", return + break; + } cookie = new Cookie(host, path); cookie.name = cookieString.substring(index, equalIndex); if (cookieString.charAt(equalIndex + 1) == QUOTATION) { @@ -757,7 +762,7 @@ public final class CookieManager { break; } } - int semicolonIndex = cookieString.indexOf(SEMICOLON, index); + semicolonIndex = cookieString.indexOf(SEMICOLON, index); if (semicolonIndex == -1) { semicolonIndex = length; } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index f61ce40..753267f 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -180,9 +180,6 @@ public class WebView extends AbsoluteLayout */ VelocityTracker mVelocityTracker; - private static boolean mShowZoomTutorial = true; - private static final int ZOOM_TUTORIAL_DURATION = 3000; - /** * Touch mode */ @@ -223,16 +220,12 @@ public class WebView extends AbsoluteLayout */ // pre-computed square of ViewConfiguration.getScaledTouchSlop() private int mTouchSlopSquare; - // pre-computed square of the density adjusted double tap slop - private int mDoubleTapSlopSquare; // pre-computed density adjusted navigation slop private int mNavSlop; // This should be ViewConfiguration.getTapTimeout() // But system time out is 100ms, which is too short for the browser. // In the browser, if it switches out of tap too soon, jump tap won't work. private static final int TAP_TIMEOUT = 200; - // The duration in milliseconds we will wait to see if it is a double tap. - private static final int DOUBLE_TAP_TIMEOUT = 250; // This should be ViewConfiguration.getLongPressTimeout() // But system time out is 500ms, which is too short for the browser. // With a short timeout, it's difficult to treat trigger a short press. @@ -258,11 +251,6 @@ public class WebView extends AbsoluteLayout private int mContentWidth; // cache of value from WebViewCore private int mContentHeight; // cache of value from WebViewCore - static int MAX_FLOAT_CONTENT_WIDTH = 480; - // the calculated minimum content width for calculating the minimum scale. - // If it is 0, it means don't use it. - private int mMinContentWidth; - // Need to have the separate control for horizontal and vertical scrollbar // style than the View's single scrollbar style private boolean mOverlayHorizontalScrollbar = true; @@ -288,11 +276,9 @@ public class WebView extends AbsoluteLayout private static final int NEVER_REMEMBER_PASSWORD = 2; private static final int SWITCH_TO_SHORTPRESS = 3; private static final int SWITCH_TO_LONGPRESS = 4; - private static final int RELEASE_SINGLE_TAP = 5; private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6; private static final int SWITCH_TO_ENTER = 7; private static final int RESUME_WEBCORE_UPDATE = 8; - private static final int DISMISS_ZOOM_TUTORIAL = 9; //! arg1=x, arg2=y static final int SCROLL_TO_MSG_ID = 10; @@ -321,7 +307,7 @@ public class WebView extends AbsoluteLayout "NEVER_REMEMBER_PASSWORD", // = 2; "SWITCH_TO_SHORTPRESS", // = 3; "SWITCH_TO_LONGPRESS", // = 4; - "RELEASE_SINGLE_TAP", // = 5; + "5", "UPDATE_TEXT_ENTRY_ADAPTER", // = 6; "SWITCH_TO_ENTER", // = 7; "RESUME_WEBCORE_UPDATE", // = 8; @@ -346,13 +332,14 @@ public class WebView extends AbsoluteLayout }; // width which view is considered to be fully zoomed out - static final int ZOOM_OUT_WIDTH = 1024; + static final int ZOOM_OUT_WIDTH = 1008; - private static final float DEFAULT_MAX_ZOOM_SCALE = 2; - private static final float DEFAULT_MIN_ZOOM_SCALE = (float) 1/3; + private static final float DEFAULT_MAX_ZOOM_SCALE = 4.0f; + private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f; // scale limit, which can be set through viewport meta tag in the web page private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; + private boolean mMinZoomScaleFixed = false; // initial scale in percent. 0 means using default. private int mInitialScale = 0; @@ -562,8 +549,8 @@ public class WebView extends AbsoluteLayout private void initZoomController(Context context) { // Create the buttons controller - mZoomButtonsController = new ZoomButtonsController(context, this); - mZoomButtonsController.setCallback(mZoomListener); + mZoomButtonsController = new ZoomButtonsController(this); + mZoomButtonsController.setOnZoomListener(mZoomListener); // Create the accessory buttons LayoutInflater inflater = @@ -611,12 +598,6 @@ public class WebView extends AbsoluteLayout final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mTouchSlopSquare = slop * slop; mMinLockSnapReverseDistance = slop; - // use twice the line height, 32 based on our current default font, for - // the double tap slop as the ViewConfiguration's double tap slop, 100, - // is too big for the Browser - final int doubleTapslop = (int) (32 * getContext().getResources() - .getDisplayMetrics().density); - mDoubleTapSlopSquare = doubleTapslop * doubleTapslop; // use one line height, 16 based on our current default font, for how // far we allow a touch be away from the edge of a link mNavSlop = (int) (16 * getContext().getResources() @@ -1646,8 +1627,7 @@ public class WebView extends AbsoluteLayout * @return true if new values were sent */ private boolean sendViewSizeZoom() { - int viewWidth = getViewWidth(); - int newWidth = Math.round(viewWidth * mInvActualScale); + int newWidth = Math.round(getViewWidth() * mInvActualScale); int newHeight = Math.round(getViewHeight() * mInvActualScale); /* * Because the native side may have already done a layout before the @@ -1663,7 +1643,7 @@ public class WebView extends AbsoluteLayout // Avoid sending another message if the dimensions have not changed. if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) { mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, - newWidth, newHeight, new Integer(viewWidth)); + newWidth, newHeight, new Float(mActualScale)); mLastWidthSent = newWidth; mLastHeightSent = newHeight; return true; @@ -3341,19 +3321,10 @@ public class WebView extends AbsoluteLayout mZoomCenterX = getViewWidth() * .5f; mZoomCenterY = getViewHeight() * .5f; - // update mMinZoomScale - if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) { - boolean atMin = Math.abs(mActualScale - mMinZoomScale) < 0.01f; - 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 - // mode, rotate it to the landscape modifying the scale to the - // new minimum zoom scale, when rotating back, we would like to - // keep the minimum zoom scale instead of keeping the same scale - // as normally we do. - mActualScale = mMinZoomScale; - } + // update mMinZoomScale if the minimum zoom scale is not fixed + if (!mMinZoomScaleFixed) { + mMinZoomScale = (float) getViewWidth() + / Math.max(ZOOM_OUT_WIDTH, mContentWidth); } // we always force, in case our height changed, in which case we still @@ -3411,15 +3382,6 @@ public class WebView extends AbsoluteLayout return false; } - if (mShowZoomTutorial && getSettings().supportZoom() - && (mMaxZoomScale != mMinZoomScale)) { - ZoomButtonsController.showZoomTutorialOnce(mContext); - mShowZoomTutorial = false; - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(DISMISS_ZOOM_TUTORIAL), - ZOOM_TUTORIAL_DURATION); - } - if (LOGV_ENABLED) { Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" + mTouchMode); @@ -3482,29 +3444,6 @@ public class WebView extends AbsoluteLayout nativeMoveSelection(viewToContent(mSelectX) , viewToContent(mSelectY), false); mTouchSelection = mExtendSelection = true; - } else if (!ZoomButtonsController.useOldZoom(mContext) - && mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { - mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); - if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { - // Found doubletap, invoke the zoom controller - int contentX = viewToContent((int) mLastTouchX - + mScrollX); - int contentY = viewToContent((int) mLastTouchY - + mScrollY); - if (inEditingMode()) { - mTextEntry.updateCachedTextfield(); - } - nativeClearFocus(contentX, contentY); - if (mLogEvent) { - EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, - (eventTime - mLastTouchUpTime), eventTime); - } - return mZoomButtonsController.handleDoubleTapEvent(ev); - } else { - // commit the short press action - doShortPress(); - // continue, mTouchMode should be still TOUCH_INIT_MODE - } } else { mTouchMode = TOUCH_INIT_MODE; mPreventDrag = mForwardTouchEvents; @@ -3588,6 +3527,12 @@ public class WebView extends AbsoluteLayout mWebViewCore .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0); } + if (getSettings().supportZoom() + && !mZoomButtonsController.isVisible() + && (canZoomScrollOut() || + mMinZoomScale < mMaxZoomScale)) { + mZoomButtonsController.setVisible(true); + } } // do pan @@ -3660,16 +3605,12 @@ public class WebView extends AbsoluteLayout mLastTouchUpTime = eventTime; switch (mTouchMode) { case TOUCH_INIT_MODE: // tap + case TOUCH_SHORTPRESS_START_MODE: + case TOUCH_SHORTPRESS_MODE: mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); - if (getSettings().supportZoom()) { - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP), - DOUBLE_TAP_TIMEOUT); - } else { - // do short press now - mTouchMode = TOUCH_DONE_MODE; - doShortPress(); - } + mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); + mTouchMode = TOUCH_DONE_MODE; + doShortPress(); break; case TOUCH_SELECT_MODE: commitCopy(); @@ -3697,28 +3638,6 @@ public class WebView extends AbsoluteLayout invalidate(); } break; - case TOUCH_SHORTPRESS_START_MODE: - case TOUCH_SHORTPRESS_MODE: { - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - if (eventTime - mLastTouchTime < TAP_TIMEOUT - && getSettings().supportZoom()) { - // Note: window manager will not release ACTION_UP - // until all the previous action events are - // returned. If GC happens, it can cause - // SWITCH_TO_SHORTPRESS message fired before - // ACTION_UP sent even time stamp of ACTION_UP is - // less than the tap time out. We need to treat this - // as tap instead of short press. - mTouchMode = TOUCH_INIT_MODE; - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP), - DOUBLE_TAP_TIMEOUT); - } else { - mTouchMode = TOUCH_DONE_MODE; - doShortPress(); - } - break; - } case TOUCH_DRAG_MODE: // if the user waits a while w/o moving before the // up, we don't want to do a fling @@ -3759,7 +3678,6 @@ public class WebView extends AbsoluteLayout } mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); mTouchMode = TOUCH_DONE_MODE; int contentX = viewToContent((int) mLastTouchX + mScrollX); int contentY = viewToContent((int) mLastTouchY + mScrollY); @@ -4132,31 +4050,7 @@ public class WebView extends AbsoluteLayout } } - /** - * An InvisibleView is an invisible, zero-sized View for backwards - * compatibility - */ - private final class InvisibleView extends View { - - private InvisibleView(Context context) { - super(context); - setVisibility(GONE); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(0, 0); - } - - @Override - public void draw(Canvas canvas) { - } - - @Override - protected void dispatchDraw(Canvas canvas) { - } - } - + // TODO: deprecate /** * Returns a view containing zoom controls i.e. +/- buttons. The caller is * in charge of installing this view to the view hierarchy. This view will @@ -4167,7 +4061,19 @@ public class WebView extends AbsoluteLayout * an invisible dummy view for backwards compatibility. */ public View getZoomControls() { - return new InvisibleView(mContext); + return mZoomButtonsController.getDummyZoomControls(); + } + + /** + * Gets the {@link ZoomButtonsController} which can be used to add + * additional buttons to the zoom controls window. + * + * @return The instance of {@link ZoomButtonsController} used by this class, + * or null if it is unavailable. + * @hide pending API council + */ + public ZoomButtonsController getZoomButtonsController() { + return mZoomButtonsController; } /** @@ -4447,11 +4353,6 @@ public class WebView extends AbsoluteLayout updateTextEntry(); break; } - case RELEASE_SINGLE_TAP: { - mTouchMode = TOUCH_DONE_MODE; - doShortPress(); - break; - } case SWITCH_TO_ENTER: if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER"); mTouchMode = TOUCH_DONE_MODE; @@ -4498,10 +4399,9 @@ public class WebView extends AbsoluteLayout 0, 0); } } - mMinContentWidth = msg.arg1; - if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) { + if (!mMinZoomScaleFixed) { mMinZoomScale = (float) getViewWidth() - / draw.mWidthHeight.x; + / Math.max(ZOOM_OUT_WIDTH, 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 @@ -4561,8 +4461,10 @@ public class WebView extends AbsoluteLayout int minScale = (Integer) scaleLimit.get("minScale"); if (minScale == 0) { mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; + mMinZoomScaleFixed = false; } else { mMinZoomScale = (float) (minScale / 100.0); + mMinZoomScaleFixed = true; } int maxScale = (Integer) scaleLimit.get("maxScale"); if (maxScale == 0) { @@ -4690,10 +4592,6 @@ public class WebView extends AbsoluteLayout } break; - case DISMISS_ZOOM_TUTORIAL: - mZoomButtonsController.finishZoomTutorial(); - break; - default: super.handleMessage(msg); break; diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index e10ffa1..3e4daf7 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -725,7 +725,7 @@ final class WebViewCore { case VIEW_SIZE_CHANGED: viewSizeChanged(msg.arg1, msg.arg2, - ((Integer) msg.obj).intValue()); + ((Float) msg.obj).floatValue()); break; case SET_SCROLL_OFFSET: @@ -1181,23 +1181,16 @@ final class WebViewCore { private int mCurrentViewWidth = 0; private int mCurrentViewHeight = 0; - // Define a minimum screen width so that we won't wrap the paragraph to one - // word per line during zoom-in. - private static final int MIN_SCREEN_WIDTH = 160; - // notify webkit that our virtual view size changed size (after inv-zoom) - private void viewSizeChanged(int w, int h, int viewWidth) { + private void viewSizeChanged(int w, int h, float scale) { if (LOGV_ENABLED) Log.v(LOGTAG, "CORE onSizeChanged"); if (w == 0) { 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)) { int width = mViewportWidth; - int screenWidth = Math.max(w, MIN_SCREEN_WIDTH); if (mViewportWidth == -1) { if (mSettings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NORMAL) { @@ -1215,19 +1208,11 @@ final class WebViewCore { * In the worse case, the native width will be adjusted when * next zoom or screen orientation change happens. */ - int minContentWidth = nativeGetContentMinPrefWidth(); - if (minContentWidth > WebView.MAX_FLOAT_CONTENT_WIDTH) { - // keep the same width and screen width so that there is - // no reflow when zoom-out - width = minContentWidth; - screenWidth = Math.min(screenWidth, Math.abs(viewWidth)); - } else { - width = Math.max(w, minContentWidth); - } + width = Math.max(w, nativeGetContentMinPrefWidth()); } } - nativeSetSize(width, Math.round((float) width * h / w), - screenWidth, scale, w, h); + nativeSetSize(width, Math.round((float) width * h / w), w, scale, + w, h); } else { nativeSetSize(w, h, w, scale, w, h); } @@ -1289,10 +1274,7 @@ final class WebViewCore { draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight); if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID"); Message.obtain(mWebView.mPrivateHandler, - WebView.NEW_PICTURE_MSG_ID, - mViewportMinimumScale == 0 ? nativeGetContentMinPrefWidth() - : 0, - 0, draw).sendToTarget(); + WebView.NEW_PICTURE_MSG_ID, draw).sendToTarget(); if (mWebkitScrollX != 0 || mWebkitScrollY != 0) { // as we have the new picture, try to sync the scroll position Message.obtain(mWebView.mPrivateHandler, @@ -1533,11 +1515,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 screen width to indicate that WebCore should - // reuse the current scale + // Send a negative scale to indicate that WebCore should reuse the + // current scale mEventHub.sendMessage(Message.obtain(null, EventHub.VIEW_SIZE_CHANGED, mWebView.mLastWidthSent, - mWebView.mLastHeightSent, -mWebView.mLastWidthSent)); + mWebView.mLastHeightSent, -1.0f)); } mBrowserFrame.didFirstLayout(); diff --git a/core/java/android/webkit/gears/DesktopAndroid.java b/core/java/android/webkit/gears/DesktopAndroid.java index ee8ca49..a7a144b 100644 --- a/core/java/android/webkit/gears/DesktopAndroid.java +++ b/core/java/android/webkit/gears/DesktopAndroid.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.provider.Browser; import android.util.Log; import android.webkit.WebView; @@ -78,7 +79,10 @@ public class DesktopAndroid { Intent viewWebPage = new Intent(Intent.ACTION_VIEW); viewWebPage.setData(Uri.parse(url)); - viewWebPage.addCategory(Intent.CATEGORY_BROWSABLE); + long urlHash = url.hashCode(); + long uniqueId = (urlHash << 32) | viewWebPage.hashCode(); + viewWebPage.putExtra(Browser.EXTRA_APPLICATION_ID, + Long.toString(uniqueId)); Intent intent = new Intent(ACTION_INSTALL_SHORTCUT); intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, viewWebPage); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 7fc96fc..bd4bba8 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -982,6 +982,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mSelectorRect.setEmpty(); invalidate(); } + + /** + * The list is empty and we need to change the layout, so *really* clear everything out. + * @hide - for AutoCompleteTextView & SearchDialog only + */ + /* package */ void resetListAndClearViews() { + rememberSyncState(); + removeAllViewsInLayout(); + mRecycler.clear(); + mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); + requestLayout(); + } @Override protected int computeVerticalScrollExtent() { @@ -1422,7 +1434,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final View v = getChildAt(mSelectedPosition - mFirstPosition); - if (v != null) v.setPressed(true); + if (v != null) { + if (v.hasFocusable()) return; + v.setPressed(true); + } setPressed(true); final boolean longClickable = isLongClickable(); diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 7b9670b..e613541 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -630,6 +630,16 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } /** + * We're changing the adapter and its views so really, really clear everything out + * @hide - for SearchDialog only + */ + public void resetListAndClearViews() { + if (mDropDownList != null) { + mDropDownList.resetListAndClearViews(); + } + } + + /** * <p>Starts filtering the content of the drop down list. The filtering * pattern is the content of the edit box. Subclasses should override this * method to filter with a different pattern, for instance a substring of diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index f646ab5..441414a 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -763,7 +763,7 @@ public class ProgressBar extends View { @Override public void invalidateDrawable(Drawable dr) { if (!mInDrawing) { - if (dr == mProgressDrawable || dr == mIndeterminateDrawable) { + if (verifyDrawable(dr)) { final Rect dirty = dr.getBounds(); final int scrollX = mScrollX + mPaddingLeft; final int scrollY = mScrollY + mPaddingTop; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 426d711..7b62b50 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6208,7 +6208,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return superResult; } - if (mMovement != null && mText instanceof Spannable && mLayout != null) { + if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) { if (action == MotionEvent.ACTION_DOWN) { mScrolled = false; @@ -6219,7 +6219,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int oldSelStart = Selection.getSelectionStart(mText); int oldSelEnd = Selection.getSelectionEnd(mText); - handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); + if (mMovement != null) { + handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); + } if (mText instanceof Editable && onCheckIsTextEditor()) { if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) { diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index 6729fd1..4daa419 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -24,12 +24,15 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.provider.Settings; +import android.util.Log; +import android.view.GestureDetector; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -37,31 +40,51 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.ViewRoot; import android.view.Window; import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.WindowManager.LayoutParams; -// TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration) - +/* + * Implementation notes: + * - The zoom controls are displayed in their own window. + * (Easier for the client and better performance) + * - This window is not touchable, and by default is not focusable. + * - To make the buttons clickable, it attaches a OnTouchListener to the owner + * view and does the hit detection locally. + * - When it is focusable, it forwards uninteresting events to the owner view's + * view hierarchy. + */ /** - * TODO: Docs - * + * The {@link ZoomButtonsController} handles showing and hiding the zoom + * controls relative to an owner view. It also gives the client access to the + * zoom controls container, allowing for additional accessory buttons to be + * shown in the zoom controls window. + * <p> + * Typical usage involves the client using the {@link GestureDetector} to + * forward events from + * {@link GestureDetector.OnDoubleTapListener#onDoubleTapEvent(MotionEvent)} to + * {@link #handleDoubleTapEvent(MotionEvent)}. Also, whenever the owner cannot + * be zoomed further, the client should update + * {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}. + * <p> * If you are using this with a custom View, please call * {@link #setVisible(boolean) setVisible(false)} from the * {@link View#onDetachedFromWindow}. - * + * * @hide */ -public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyListener { +public class ZoomButtonsController implements View.OnTouchListener { private static final String TAG = "ZoomButtonsController"; private static final int ZOOM_CONTROLS_TIMEOUT = (int) ViewConfiguration.getZoomControlsTimeout(); - // TODO: scaled to density private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20; + private int mTouchPaddingScaledSq; private Context mContext; private WindowManager mWindowManager; @@ -72,17 +95,17 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi private View mOwnerView; /** - * The bounds of the owner view in global coordinates. This is recalculated + * The location of the owner view on the screen. This is recalculated * each time the zoom controller is shown. */ - private Rect mOwnerViewBounds = new Rect(); + private int[] mOwnerViewRawLocation = new int[2]; /** * The container that is added as a window. */ private FrameLayout mContainer; private LayoutParams mContainerLayoutParams; - private int[] mContainerLocation = new int[2]; + private int[] mContainerRawLocation = new int[2]; private ZoomControls mControls; @@ -94,7 +117,7 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi /** * The {@link #mTouchTargetView}'s location in window, set on touch down. */ - private int[] mTouchTargetLocationInWindow = new int[2]; + private int[] mTouchTargetWindowLocation = new int[2]; /** * If the zoom controller is dismissed but the user is still in a touch * interaction, we set this to true. This will ignore all touch events until @@ -102,15 +125,28 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi */ private boolean mReleaseTouchListenerOnUp; + /** + * Whether we are currently in the double-tap gesture, with the second tap + * still being performed (i.e., we're waiting for the second tap's touch up). + */ private boolean mIsSecondTapDown; + /** Whether the container has been added to the window manager. */ private boolean mIsVisible; private Rect mTempRect = new Rect(); - + private int[] mTempIntArray = new int[2]; + private OnZoomListener mCallback; /** + * In 1.0, the ZoomControls were to be added to the UI by the client of + * WebView, MapView, etc. We didn't want apps to break, so we return a dummy + * view in place now. + */ + private InvisibleView mDummyZoomControls; + + /** * When showing the zoom, we add the view as a new window. However, there is * logic that needs to know the size of the zoom which is determined after * it's laid out. Therefore, we must post this logic onto the UI thread so @@ -121,6 +157,9 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi private IntentFilter mConfigurationChangedFilter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); + /** + * Needed to reposition the zoom controls after configuration changes. + */ private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -161,41 +200,68 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi case MSG_POST_SET_VISIBLE: if (mOwnerView.getWindowToken() == null) { - // Doh, it is still null, throw an exception - throw new IllegalArgumentException( + // Doh, it is still null, just ignore the set visible call + Log.e(TAG, "Cannot make the zoom controller visible if the owner view is " + "not attached to a window."); + } else { + setVisible(true); } - setVisible(true); break; } } }; - public ZoomButtonsController(Context context, View ownerView) { - mContext = context; - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + /** + * Constructor for the {@link ZoomButtonsController}. + * + * @param ownerView The view that is being zoomed by the zoom controls. The + * zoom controls will be displayed aligned with this view. + */ + public ZoomButtonsController(View ownerView) { + mContext = ownerView.getContext(); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mOwnerView = ownerView; + mTouchPaddingScaledSq = (int) + (ZOOM_CONTROLS_TOUCH_PADDING * mContext.getResources().getDisplayMetrics().density); + mTouchPaddingScaledSq *= mTouchPaddingScaledSq; + mContainer = createContainer(); } + /** + * Whether to enable the zoom in control. + * + * @param enabled Whether to enable the zoom in control. + */ public void setZoomInEnabled(boolean enabled) { mControls.setIsZoomInEnabled(enabled); } + /** + * Whether to enable the zoom out control. + * + * @param enabled Whether to enable the zoom out control. + */ public void setZoomOutEnabled(boolean enabled) { mControls.setIsZoomOutEnabled(enabled); } + /** + * Sets the delay between zoom callbacks as the user holds a zoom button. + * + * @param speed The delay in milliseconds between zoom callbacks. + */ public void setZoomSpeed(long speed) { mControls.setZoomSpeed(speed); } private FrameLayout createContainer() { LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.BOTTOM | Gravity.CENTER; + // Controls are positioned BOTTOM | CENTER with respect to the owner view. + lp.gravity = Gravity.TOP | Gravity.LEFT; lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_NO_LIMITS; @@ -206,10 +272,9 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; mContainerLayoutParams = lp; - FrameLayout container = new FrameLayout(mContext); + FrameLayout container = new Container(mContext); container.setLayoutParams(lp); container.setMeasureAllChildren(true); - container.setOnKeyListener(this); LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -232,30 +297,51 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi return container; } - public void setCallback(OnZoomListener callback) { - mCallback = callback; + /** + * Sets the {@link OnZoomListener} listener that receives callbacks to zoom. + * + * @param listener The listener that will be told to zoom. + */ + public void setOnZoomListener(OnZoomListener listener) { + mCallback = listener; } + /** + * Sets whether the zoom controls should be focusable. If the controls are + * focusable, then trackball and arrow key interactions are possible. + * Otherwise, only touch interactions are possible. + * + * @param focusable Whether the zoom controls should be focusable. + */ public void setFocusable(boolean focusable) { + int oldFlags = mContainerLayoutParams.flags; if (focusable) { mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE; } else { mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE; } - if (mIsVisible) { + if ((mContainerLayoutParams.flags != oldFlags) && mIsVisible) { mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); } } + /** + * Whether the zoom controls are visible to the user. + * + * @return Whether the zoom controls are visible to the user. + */ public boolean isVisible() { return mIsVisible; } + /** + * Sets whether the zoom controls should be visible to the user. + * + * @param visible Whether the zoom controls should be visible to the user. + */ public void setVisible(boolean visible) { - if (!useThisZoom(mContext)) return; - if (visible) { if (mOwnerView.getWindowToken() == null) { /* @@ -329,12 +415,13 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi } /** - * TODO: docs - * - * Notes: - * - Please ensure you set your View to INVISIBLE not GONE when hiding it. - * - * @return TODO + * Gets the container that is the parent of the zoom controls. + * <p> + * The client can add other views to this container to link them with the + * zoom controls. + * + * @return The container of the zoom controls. It will be a layout that + * respects the gravity of a child's layout parameters. */ public ViewGroup getContainer() { return mContainer; @@ -347,14 +434,12 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi /** * Should be called by the client for each event belonging to the second tap - * (the down, move, up, and cancel events). + * (the down, move, up, and/or cancel events). * * @param event The event belonging to the second tap. * @return Whether the event was consumed. */ public boolean handleDoubleTapEvent(MotionEvent event) { - if (!useThisZoom(mContext)) return false; - int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { @@ -382,9 +467,28 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi } private void refreshPositioningVariables() { + // Position the zoom controls on the bottom of the owner view. + int ownerHeight = mOwnerView.getHeight(); + int ownerWidth = mOwnerView.getWidth(); + // The gap between the top of the owner and the top of the container + int containerOwnerYOffset = ownerHeight - mContainer.getHeight(); + // Calculate the owner view's bounds - mOwnerView.getGlobalVisibleRect(mOwnerViewBounds); - mContainer.getLocationOnScreen(mContainerLocation); + mOwnerView.getLocationOnScreen(mOwnerViewRawLocation); + mContainerRawLocation[0] = mOwnerViewRawLocation[0]; + mContainerRawLocation[1] = mOwnerViewRawLocation[1] + containerOwnerYOffset; + + int[] ownerViewWindowLoc = mTempIntArray; + mOwnerView.getLocationInWindow(ownerViewWindowLoc); + + // lp.x and lp.y should be relative to the owner's window top-left + mContainerLayoutParams.x = ownerViewWindowLoc[0]; + mContainerLayoutParams.width = ownerWidth; + mContainerLayoutParams.y = ownerViewWindowLoc[1] + containerOwnerYOffset; + if (mIsVisible) { + mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); + } + } /** @@ -396,11 +500,65 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi } } - public boolean onKey(View v, int keyCode, KeyEvent event) { - dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); - return false; + /* This will only be called when the container has focus. */ + private boolean onContainerKey(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (isInterestingKey(keyCode)) { + + if (keyCode == KeyEvent.KEYCODE_BACK) { + setVisible(false); + } else { + dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); + } + + // Let the container handle the key + return false; + + } else { + + ViewRoot viewRoot = getOwnerViewRoot(); + if (viewRoot != null) { + viewRoot.dispatchKey(event); + } + + // We gave the key to the owner, don't let the container handle this key + return true; + } } + private boolean isInterestingKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_BACK: + return true; + default: + return false; + } + } + + private ViewRoot getOwnerViewRoot() { + View rootViewOfOwner = mOwnerView.getRootView(); + if (rootViewOfOwner == null) { + return null; + } + + ViewParent parentOfRootView = rootViewOfOwner.getParent(); + if (parentOfRootView instanceof ViewRoot) { + return (ViewRoot) parentOfRootView; + } else { + return null; + } + } + + /** + * @hide The ZoomButtonsController implements the OnTouchListener, but this + * does not need to be shown in its public API. + */ public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); @@ -423,14 +581,13 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi return true; } - // TODO: optimize this (it ends up removing message and queuing another) dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); View targetView = mTouchTargetView; switch (action) { case MotionEvent.ACTION_DOWN: - targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY()); + targetView = findViewForTouch((int) event.getRawX(), (int) event.getRawY()); setTouchTargetView(targetView); break; @@ -442,14 +599,22 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi if (targetView != null) { // The upperleft corner of the target view in raw coordinates - int targetViewRawX = mContainerLocation[0] + mTouchTargetLocationInWindow[0]; - int targetViewRawY = mContainerLocation[1] + mTouchTargetLocationInWindow[1]; + int targetViewRawX = mContainerRawLocation[0] + mTouchTargetWindowLocation[0]; + int targetViewRawY = mContainerRawLocation[1] + mTouchTargetWindowLocation[1]; MotionEvent containerEvent = MotionEvent.obtain(event); // Convert the motion event into the target view's coordinates (from // owner view's coordinates) - containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX, - mOwnerViewBounds.top - targetViewRawY); + containerEvent.offsetLocation(mOwnerViewRawLocation[0] - targetViewRawX, + mOwnerViewRawLocation[1] - targetViewRawY); + /* Disallow negative coordinates (which can occur due to + * ZOOM_CONTROLS_TOUCH_PADDING) */ + if (containerEvent.getX() < 0) { + containerEvent.offsetLocation(-containerEvent.getX(), 0); + } + if (containerEvent.getY() < 0) { + containerEvent.offsetLocation(0, -containerEvent.getY()); + } boolean retValue = targetView.dispatchTouchEvent(containerEvent); containerEvent.recycle(); return retValue || consumeEvent; @@ -462,7 +627,7 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi private void setTouchTargetView(View view) { mTouchTargetView = view; if (view != null) { - view.getLocationInWindow(mTouchTargetLocationInWindow); + view.getLocationInWindow(mTouchTargetWindowLocation); } } @@ -473,11 +638,15 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi * @param rawY The raw Y. * @return The view that should receive the touches, or null if there is not one. */ - private View getViewForTouch(int rawX, int rawY) { + private View findViewForTouch(int rawX, int rawY) { // Reverse order so the child drawn on top gets first dibs. - int containerCoordsX = rawX - mContainerLocation[0]; - int containerCoordsY = rawY - mContainerLocation[1]; + int containerCoordsX = rawX - mContainerRawLocation[0]; + int containerCoordsY = rawY - mContainerRawLocation[1]; Rect frame = mTempRect; + + View closestChild = null; + int closestChildDistanceSq = Integer.MAX_VALUE; + for (int i = mContainer.getChildCount() - 1; i >= 0; i--) { View child = mContainer.getChildAt(i); if (child.getVisibility() != View.VISIBLE) { @@ -485,14 +654,24 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi } child.getHitRect(frame); - // Expand the touch region - frame.top -= ZOOM_CONTROLS_TOUCH_PADDING; if (frame.contains(containerCoordsX, containerCoordsY)) { return child; } + + int distanceX = Math.min(Math.abs(frame.left - containerCoordsX), + Math.abs(containerCoordsX - frame.right)); + int distanceY = Math.min(Math.abs(frame.top - containerCoordsY), + Math.abs(containerCoordsY - frame.bottom)); + int distanceSq = distanceX * distanceX + distanceY * distanceY; + + if ((distanceSq < mTouchPaddingScaledSq) && + (distanceSq < closestChildDistanceSq)) { + closestChild = child; + closestChildDistanceSq = distanceSq; + } } - return null; + return closestChild; } private void onPostConfigurationChanged() { @@ -518,6 +697,10 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi * gallery */ public static void showZoomTutorialOnce(Context context) { + + // TODO: remove this code, but to hit the weekend build, just never show + if (true) return; + ContentResolver cr = context.getContentResolver(); if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) { return; @@ -583,22 +766,83 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi finishZoomTutorial(mContext, true); } - // Temporary methods for different zoom types - static int getZoomType(Context context) { - return Settings.System.getInt(context.getContentResolver(), "zoom", 2); - } - - public static boolean useOldZoom(Context context) { - return getZoomType(context) == 0; - } - - public static boolean useThisZoom(Context context) { - return getZoomType(context) == 2; + /** @hide Should only be used only be WebView and MapView */ + public View getDummyZoomControls() { + if (mDummyZoomControls == null) { + mDummyZoomControls = new InvisibleView(mContext); + } + return mDummyZoomControls; } - + + /** + * Interface that will be called when the user performs an interaction that + * triggers some action, for example zooming. + */ public interface OnZoomListener { + /** + * Called when the given point should be centered. The point will be in + * owner view coordinates. + * + * @param x The x of the point. + * @param y The y of the point. + */ void onCenter(int x, int y); + + /** + * Called when the zoom controls' visibility changes. + * + * @param visible Whether the zoom controls are visible. + */ void onVisibilityChanged(boolean visible); + + /** + * Called when the owner view needs to be zoomed. + * + * @param zoomIn The direction of the zoom: true to zoom in, false to zoom out. + */ void onZoom(boolean zoomIn); } + + private class Container extends FrameLayout { + public Container(Context context) { + super(context); + } + + /* + * Need to override this to intercept the key events. Otherwise, we + * would attach a key listener to the container but its superclass + * ViewGroup gives it to the focused View instead of calling the key + * listener, and so we wouldn't get the events. + */ + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return onContainerKey(event) ? true : super.dispatchKeyEvent(event); + } + } + + /** + * An InvisibleView is an invisible, zero-sized View for backwards + * compatibility + */ + private final class InvisibleView extends View { + + private InvisibleView(Context context) { + super(context); + setVisibility(GONE); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(0, 0); + } + + @Override + public void draw(Canvas canvas) { + } + + @Override + protected void dispatchDraw(Canvas canvas) { + } + } + } diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java index e978db8..84d8f0e 100644 --- a/core/java/android/widget/ZoomControls.java +++ b/core/java/android/widget/ZoomControls.java @@ -29,8 +29,12 @@ import com.android.internal.R; /** * The {@code ZoomControls} class displays a simple set of controls used for zooming and - * provides callbacks to register for events. - */ + * provides callbacks to register for events. */ +// TODO: pending API council +// * <p> +// * Instead of using this directly, consider using the {@link ZoomButtonsController} which +// * handles displaying the zoom controls. +// */ @Widget public class ZoomControls extends LinearLayout { @@ -81,9 +85,7 @@ public class ZoomControls extends LinearLayout { } public void show() { - if (ZoomButtonsController.useOldZoom(mContext)) { - fade(View.VISIBLE, 0.0f, 1.0f); - } + fade(View.VISIBLE, 0.0f, 1.0f); } public void hide() { diff --git a/core/java/com/android/internal/widget/EditStyledText.java b/core/java/com/android/internal/widget/EditStyledText.java index 48b4780..8a4675a 100644 --- a/core/java/com/android/internal/widget/EditStyledText.java +++ b/core/java/com/android/internal/widget/EditStyledText.java @@ -16,20 +16,26 @@ package com.android.internal.widget; +import android.app.AlertDialog.Builder; import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; import android.text.Editable; +import android.text.Html; import android.text.Spannable; import android.text.style.AbsoluteSizeSpan; import android.text.style.ForegroundColorSpan; +import android.text.style.ImageSpan; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.EditText; /** - * EditStyledText extends EditText for managing the flow and status - * to edit the styled text. This manages the states and flows of editing, - * supports inserting image, import/export HTML. + * EditStyledText extends EditText for managing the flow and status to edit + * the styled text. This manages the states and flows of editing, supports + * inserting image, import/export HTML. */ public class EditStyledText extends EditText { @@ -61,7 +67,7 @@ public class EditStyledText extends EditText { public static final int STATE_SELECT_ON = 1; /** The state that selection is done, but not fixed. */ public static final int STATE_SELECTED = 2; - /** The state that selection is done and not fixed.*/ + /** The state that selection is done and not fixed. */ public static final int STATE_SELECT_FIX = 3; /** @@ -73,26 +79,28 @@ public class EditStyledText extends EditText { public static final int HINT_MSG_SELECT_END = 3; public static final int HINT_MSG_PUSH_COMPETE = 4; - /** - * EditStyledTextInterface provides functions for notifying messages - * to calling class. + * EditStyledTextInterface provides functions for notifying messages to + * calling class. */ - public interface EditStyledTextInterface { - public void notifyHintMsg(int msg_id); + public interface EditStyledTextNotifier { + public void notifyHintMsg(int msgId); } - private EditStyledTextInterface mESTInterface; + + private EditStyledTextNotifier mESTInterface; /** - * EditStyledTextEditorManager manages the flow and status of - * each function for editing styled text. + * EditStyledTextEditorManager manages the flow and status of each + * function for editing styled text. */ - private EditStyledTextEditorManager mManager; + private EditorManager mManager; + private StyledTextConverter mConverter; + private StyledTextToast mToast; /** - * EditStyledText extends EditText for managing flow of each editing - * action. - */ + * EditStyledText extends EditText for managing flow of each editing + * action. + */ public EditStyledText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); @@ -109,11 +117,54 @@ public class EditStyledText extends EditText { } /** - * Set View objects used in EditStyledText. - * @param helptext The view shows help messages. + * Set Notifier. + */ + public void setNotifier(EditStyledTextNotifier estInterface) { + mESTInterface = estInterface; + } + + /** + * Set Builder for AlertDialog. + * + * @param builder + * Builder for opening Alert Dialog. + */ + public void setBuilder(Builder builder) { + mToast.setBuilder(builder); + } + + /** + * Set Parameters for ColorAlertDialog. + * + * @param colortitle + * Title for Alert Dialog. + * @param colornames + * List of name of selecting color. + * @param colorints + * List of int of color. + */ + public void setColorAlertParams(CharSequence colortitle, + CharSequence[] colornames, CharSequence[] colorints) { + mToast.setColorAlertParams(colortitle, colornames, colorints); + } + + /** + * Set Parameters for SizeAlertDialog. + * + * @param sizetitle + * Title for Alert Dialog. + * @param sizenames + * List of name of selecting size. + * @param sizedisplayints + * List of int of size displayed in TextView. + * @param sizesendints + * List of int of size exported to HTML. */ - public void setParts(EditStyledTextInterface est_interface) { - mESTInterface = est_interface; + public void setSizeAlertParams(CharSequence sizetitle, + CharSequence[] sizenames, CharSequence[] sizedisplayints, + CharSequence[] sizesendints) { + mToast.setSizeAlertParams(sizetitle, sizenames, sizedisplayints, + sizesendints); } @Override @@ -129,8 +180,8 @@ public class EditStyledText extends EditText { } /** - * Start editing. This function have to be called before other - * editing actions. + * Start editing. This function have to be called before other editing + * actions. */ public void onStartEdit() { mManager.onStartEdit(); @@ -186,6 +237,26 @@ public class EditStyledText extends EditText { } /** + * InsertImage to TextView by using URI + * + * @param uri + * URI of the iamge inserted to TextView. + */ + public void onInsertImage(Uri uri) { + mManager.onInsertImage(uri); + } + + /** + * InsertImage to TextView by using resource ID + * + * @param resId + * Resource ID of the iamge inserted to TextView. + */ + public void onInsertImage(int resId) { + mManager.onInsertImage(resId); + } + + /** * Fix Selected Item. */ public void fixSelectedItem() { @@ -194,7 +265,9 @@ public class EditStyledText extends EditText { /** * Set Size of the Item. - * @param size The size of the Item. + * + * @param size + * The size of the Item. */ public void setItemSize(int size) { mManager.setItemSize(size); @@ -202,14 +275,25 @@ public class EditStyledText extends EditText { /** * Set Color of the Item. - * @param color The color of the Item. + * + * @param color + * The color of the Item. */ public void setItemColor(int color) { mManager.setItemColor(color); } + public void onShowColorAlert() { + mToast.onShowColorAlertDialog(); + } + + public void onShowSizeAlert() { + mToast.onShowSizeAlertDialog(); + } + /** * Check editing is started. + * * @return Whether editing is started or not. */ public boolean isEditting() { @@ -218,6 +302,7 @@ public class EditStyledText extends EditText { /** * Get the mode of the action. + * * @return The mode of the action. */ public int getEditMode() { @@ -226,12 +311,17 @@ public class EditStyledText extends EditText { /** * Get the state of the selection. + * * @return The state of the selection. */ public int getSelectState() { return mManager.getSelectState(); } + public String getBody() { + return mConverter.getConvertedBody(); + } + /** * Initialize members. */ @@ -240,23 +330,36 @@ public class EditStyledText extends EditText { Log.d(LOG_TAG, "--- init"); requestFocus(); } - mManager = new EditStyledTextEditorManager(this); + mManager = new EditorManager(this); + mConverter = new StyledTextConverter(this); + mToast = new StyledTextToast(this); } /** * Notify hint messages what action is expected to calling class. - * @param msg + * + * @param msgId + * Id of the hint message. */ - private void setHintMessage(int msg_id) { + private void setHintMessage(int msgId) { if (mESTInterface != null) { - mESTInterface.notifyHintMsg(msg_id); + mESTInterface.notifyHintMsg(msgId); + } + } + + @Override + public Bundle getInputExtras(boolean create) { + Bundle bundle = super.getInputExtras(create); + if (bundle != null) { + bundle.putBoolean("allowEmoji", true); } + return bundle; } /** * Object which manages the flow and status of editing actions. */ - private class EditStyledTextEditorManager { + private class EditorManager { private boolean mEditFlag = false; private int mMode = 0; private int mState = 0; @@ -266,7 +369,7 @@ public class EditStyledText extends EditText { private Editable mTextSelectBuffer; private CharSequence mTextCopyBufer; - EditStyledTextEditorManager(EditStyledText est) { + EditorManager(EditStyledText est) { mEST = est; } @@ -368,6 +471,28 @@ public class EditStyledText extends EditText { handleComplete(); } + public void onInsertImage(Uri uri) { + if (DBG) { + Log.d(LOG_TAG, "--- onInsertImage by URI: " + uri.getPath() + + "," + uri.toString()); + } + + mEST.getText().append("a"); + mEST.getText().setSpan(new ImageSpan(mEST.getContext(), uri), + mEST.getText().length() - 1, mEST.getText().length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + public void onInsertImage(int resID) { + if (DBG) { + Log.d(LOG_TAG, "--- onInsertImage by resID"); + } + mEST.getText().append("b"); + mEST.getText().setSpan(new ImageSpan(mEST.getContext(), resID), + mEST.getText().length() - 1, mEST.getText().length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + public boolean isEditting() { return mEditFlag; } @@ -404,6 +529,12 @@ public class EditStyledText extends EditText { case MODE_COPY: handleCopy(); break; + case MODE_COLOR: + handleColor(); + break; + case MODE_SIZE: + handleSize(); + break; default: break; } @@ -455,12 +586,14 @@ public class EditStyledText extends EditText { Log.d(LOG_TAG, "--- handleSize: " + mMode + "," + mState); } if (!mEditFlag) { + Log.e(LOG_TAG, "--- Editing is not started for handlesize."); return; } if (mMode == MODE_NOTHING || mMode == MODE_SELECT) { mMode = MODE_SIZE; if (mState == STATE_SELECTED) { mState = STATE_SELECT_FIX; + handleSize(); } else { handleSelect(); } @@ -468,22 +601,29 @@ public class EditStyledText extends EditText { handleCancel(); mMode = MODE_SIZE; handleSize(); - } else if (mState == STATE_SELECT_FIX) { - mEST.setHintMessage(HINT_MSG_NULL); + } else { + if (mState == STATE_SELECT_FIX) { + mEST.setHintMessage(HINT_MSG_NULL); + mEST.onShowSizeAlert(); + } else { + Log.d(LOG_TAG, "--- handlesize: do nothing"); + } } } private void handleColor() { if (DBG) { - Log.d(LOG_TAG, "--- handleColor"); + Log.d(LOG_TAG, "--- handleSize: " + mMode + "," + mState); } if (!mEditFlag) { + Log.e(LOG_TAG, "--- Editing is not started for handlecolor."); return; } if (mMode == MODE_NOTHING || mMode == MODE_SELECT) { mMode = MODE_COLOR; if (mState == STATE_SELECTED) { mState = STATE_SELECT_FIX; + handleColor(); } else { handleSelect(); } @@ -491,35 +631,39 @@ public class EditStyledText extends EditText { handleCancel(); mMode = MODE_COLOR; handleSize(); - } else if (mState == STATE_SELECT_FIX) { - mEST.setHintMessage(HINT_MSG_NULL); + } else { + if (mState == STATE_SELECT_FIX) { + mEST.setHintMessage(HINT_MSG_NULL); + mEST.onShowColorAlert(); + } else { + Log.d(LOG_TAG, "--- handlecolor: do nothing"); + } } } private void handleSelect() { if (DBG) { - Log.d(LOG_TAG, "--- handleSelect" + mEditFlag + "," + mState); + Log.d(LOG_TAG, "--- handleSelect:" + mEditFlag + "," + mState); } if (!mEditFlag) { return; } if (mState == STATE_SELECT_OFF) { if (isTextSelected()) { - Log.e(LOG_TAG, "Selection state is off, but selected"); + Log.e(LOG_TAG, "Selection is off, but selected"); } setSelectStartPos(); mEST.setHintMessage(HINT_MSG_SELECT_END); } else if (mState == STATE_SELECT_ON) { if (isTextSelected()) { - Log.e(LOG_TAG, "Selection state now start, but selected"); + Log.e(LOG_TAG, "Selection now start, but selected"); } setSelectEndPos(); mEST.setHintMessage(HINT_MSG_PUSH_COMPETE); doNextHandle(); } else if (mState == STATE_SELECTED) { if (!isTextSelected()) { - Log.e(LOG_TAG, - "Selection state is done, but not selected"); + Log.e(LOG_TAG, "Selection is done, but not selected"); } setSelectEndPos(); doNextHandle(); @@ -537,6 +681,9 @@ public class EditStyledText extends EditText { } private void doNextHandle() { + if (DBG) { + Log.d(LOG_TAG, "--- doNextHandle: " + mMode + "," + mState); + } switch (mMode) { case MODE_COPY: handleCopy(); @@ -556,6 +703,9 @@ public class EditStyledText extends EditText { } private void handleResetEdit() { + if (DBG) { + Log.d(LOG_TAG, "Reset Editor"); + } handleCancel(); mEditFlag = true; mEST.setHintMessage(HINT_MSG_SELECT_START); @@ -564,7 +714,7 @@ public class EditStyledText extends EditText { // Methods of selection private void onSelect() { if (DBG) { - Log.d(LOG_TAG, "--- onSelect"); + Log.d(LOG_TAG, "--- onSelect:" + mCurStart + "," + mCurEnd); } if (mCurStart >= 0 && mCurStart <= mEST.getText().length() && mCurEnd >= 0 && mCurEnd <= mEST.getText().length()) { @@ -650,4 +800,132 @@ public class EditStyledText extends EditText { } } + private class StyledTextConverter { + private EditStyledText mEST; + + public StyledTextConverter(EditStyledText est) { + mEST = est; + } + + public String getConvertedBody() { + String htmlBody = Html.toHtml(mEST.getText()); + return htmlBody; + } + } + + private class StyledTextToast { + Builder mBuilder; + CharSequence mColorTitle; + CharSequence mSizeTitle; + CharSequence[] mColorNames; + CharSequence[] mColorInts; + CharSequence[] mSizeNames; + CharSequence[] mSizeDisplayInts; + CharSequence[] mSizeSendInts; + EditStyledText mEST; + + public StyledTextToast(EditStyledText est) { + mEST = est; + } + + public void setBuilder(Builder builder) { + mBuilder = builder; + } + + public void setColorAlertParams(CharSequence colortitle, + CharSequence[] colornames, CharSequence[] colorints) { + mColorTitle = colortitle; + mColorNames = colornames; + mColorInts = colorints; + } + + public void setSizeAlertParams(CharSequence sizetitle, + CharSequence[] sizenames, CharSequence[] sizedisplayints, + CharSequence[] sizesendints) { + mSizeTitle = sizetitle; + mSizeNames = sizenames; + mSizeDisplayInts = sizedisplayints; + mSizeSendInts = sizesendints; + } + + public boolean checkColorAlertParams() { + if (DBG) { + Log.d(LOG_TAG, "--- checkParams"); + } + if (mBuilder == null) { + Log.e(LOG_TAG, "--- builder is null."); + return false; + } else if (mColorTitle == null || mColorNames == null + || mColorInts == null) { + Log.e(LOG_TAG, "--- color alert params are null."); + return false; + } else if (mColorNames.length != mColorInts.length) { + Log.e(LOG_TAG, "--- the length of color alert params are " + + "different."); + return false; + } + return true; + } + + public boolean checkSizeAlertParams() { + if (DBG) { + Log.d(LOG_TAG, "--- checkParams"); + } + if (mBuilder == null) { + Log.e(LOG_TAG, "--- builder is null."); + } else if (mSizeTitle == null || mSizeNames == null + || mSizeDisplayInts == null || mSizeSendInts == null) { + Log.e(LOG_TAG, "--- size alert params are null."); + } else if (mSizeNames.length != mSizeDisplayInts.length + && mSizeSendInts.length != mSizeDisplayInts.length) { + Log.e(LOG_TAG, "--- the length of size alert params are " + + "different."); + } + return true; + } + + private void onShowColorAlertDialog() { + if (DBG) { + Log.d(LOG_TAG, "--- onShowAlertDialog"); + } + if (!checkColorAlertParams()) { + return; + } + mBuilder.setTitle(mColorTitle); + mBuilder.setIcon(0); + mBuilder. + setItems(mColorNames, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Log.d("EETVM", "mBuilder.onclick:" + which); + int color = Integer.parseInt( + (String) mColorInts[which], 16) - 0x01000000; + mEST.setItemColor(color); + } + }); + mBuilder.show(); + } + + private void onShowSizeAlertDialog() { + if (DBG) { + Log.d(LOG_TAG, "--- onShowAlertDialog"); + } + if (!checkColorAlertParams()) { + return; + } + mBuilder.setTitle(mSizeTitle); + mBuilder.setIcon(0); + mBuilder. + setItems(mSizeNames, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Log.d("EETVM", "mBuilder.onclick:" + which); + int size = Integer + .parseInt((String) mSizeDisplayInts[which]); + mEST.setItemSize(size); + } + }); + mBuilder.show(); + } + } } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index c8b3ad4..f0b311c 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -78,6 +78,7 @@ public class LockPatternUtils { private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; + private final static String PATTERN_EVER_CHOSEN = "lockscreen.patterneverchosen"; private final ContentResolver mContentResolver; @@ -139,6 +140,16 @@ public class LockPatternUtils { } /** + * Return true if the user has ever chosen a pattern. This is true even if the pattern is + * currently cleared. + * + * @return True if the user has ever chosen a pattern. + */ + public boolean isPatternEverChosen() { + return getBoolean(PATTERN_EVER_CHOSEN); + } + + /** * Save a lock pattern. * @param pattern The new pattern to save. */ @@ -155,6 +166,7 @@ public class LockPatternUtils { raf.write(hash, 0, hash.length); } raf.close(); + setBoolean(PATTERN_EVER_CHOSEN, true); } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings provider Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp index 1429f58..971f87c 100644 --- a/core/jni/android_os_ParcelFileDescriptor.cpp +++ b/core/jni/android_os_ParcelFileDescriptor.cpp @@ -15,6 +15,8 @@ ** limitations under the License. */ +//#define LOG_NDEBUG 0 + #include "JNIHelp.h" #include <fcntl.h> @@ -45,6 +47,11 @@ static struct socket_impl_offsets_t jfieldID mFileDescriptor; } gSocketImplOffsets; +static struct parcel_file_descriptor_offsets_t +{ + jclass mClass; + jfieldID mFileDescriptor; +} gParcelFileDescriptorOffsets; static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromSocket(JNIEnv* env, jobject clazz, jobject object) @@ -60,10 +67,21 @@ static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromSocket(JNIEn return fileDescriptorClone; } +static jint getFd(JNIEnv* env, jobject clazz) +{ + jobject descriptor = env->GetObjectField(clazz, gParcelFileDescriptorOffsets.mFileDescriptor); + if (descriptor == NULL) return -1; + return env->GetIntField(descriptor, gFileDescriptorOffsets.mDescriptor); +} + static jlong android_os_ParcelFileDescriptor_getStatSize(JNIEnv* env, jobject clazz) { - jint fd = env->GetIntField(clazz, gFileDescriptorOffsets.mDescriptor); + jint fd = getFd(env, clazz); + if (fd < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "bad file descriptor"); + return -1; + } struct stat st; if (fstat(fd, &st) != 0) { @@ -80,7 +98,12 @@ static jlong android_os_ParcelFileDescriptor_getStatSize(JNIEnv* env, static jlong android_os_ParcelFileDescriptor_seekTo(JNIEnv* env, jobject clazz, jlong pos) { - jint fd = env->GetIntField(clazz, gFileDescriptorOffsets.mDescriptor); + jint fd = getFd(env, clazz); + if (fd < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "bad file descriptor"); + return -1; + } + return lseek(fd, pos, SEEK_SET); } @@ -121,6 +144,10 @@ int register_android_os_ParcelFileDescriptor(JNIEnv* env) clazz = env->FindClass(kParcelFileDescriptorPathName); LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor"); + gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); + gParcelFileDescriptorOffsets.mFileDescriptor = env->GetFieldID(clazz, "mFileDescriptor", "Ljava/io/FileDescriptor;"); + LOG_FATAL_IF(gParcelFileDescriptorOffsets.mFileDescriptor == NULL, + "Unable to find mFileDescriptor field in android.os.ParcelFileDescriptor"); return AndroidRuntime::registerNativeMethods( env, kParcelFileDescriptorPathName, diff --git a/core/res/res/anim/app_starting_exit.xml b/core/res/res/anim/app_starting_exit.xml index c6f94b0..0675575 100644 --- a/core/res/res/anim/app_starting_exit.xml +++ b/core/res/res/anim/app_starting_exit.xml @@ -19,6 +19,6 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/dialog_enter.xml b/core/res/res/anim/dialog_enter.xml index f48dd37..cc409e8 100644 --- a/core/res/res/anim/dialog_enter.xml +++ b/core/res/res/anim/dialog_enter.xml @@ -23,7 +23,7 @@ <scale android:fromXScale="0.9" android:toXScale="1.0" android:fromYScale="0.9" android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%" - android:duration="75" /> + android:duration="@android:integer/config_shortAnimTime" /> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="75" /> + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/dialog_exit.xml b/core/res/res/anim/dialog_exit.xml index 24de6e7..8bf8082 100644 --- a/core/res/res/anim/dialog_exit.xml +++ b/core/res/res/anim/dialog_exit.xml @@ -22,7 +22,7 @@ <scale android:fromXScale="1.0" android:toXScale="0.9" android:fromYScale="1.0" android:toYScale="0.9" android:pivotX="50%" android:pivotY="50%" - android:duration="75" /> + android:duration="@android:integer/config_shortAnimTime" /> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="75"/> + android:duration="@android:integer/config_shortAnimTime"/> </set> diff --git a/core/res/res/anim/fade_in.xml b/core/res/res/anim/fade_in.xml index 32eea01..57310d9 100644 --- a/core/res/res/anim/fade_in.xml +++ b/core/res/res/anim/fade_in.xml @@ -18,4 +18,7 @@ */ --> -<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@anim/decelerate_interpolator" + android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_longAnimTime" /> diff --git a/core/res/res/anim/fade_out.xml b/core/res/res/anim/fade_out.xml index e8ae9c1..dc76276 100644 --- a/core/res/res/anim/fade_out.xml +++ b/core/res/res/anim/fade_out.xml @@ -21,5 +21,5 @@ <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="300" + android:duration="@android:integer/config_mediumAnimTime" /> diff --git a/core/res/res/anim/grow_fade_in.xml b/core/res/res/anim/grow_fade_in.xml index 0969857..5dc41cb 100644 --- a/core/res/res/anim/grow_fade_in.xml +++ b/core/res/res/anim/grow_fade_in.xml @@ -21,6 +21,9 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:fromXScale="1.0" android:toXScale="1.0" android:fromYScale="0.3" android:toYScale="1.0" - android:pivotX="0%" android:pivotY="0%" android:duration="125" /> - <alpha android:interpolator="@anim/decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" /> + android:pivotX="0%" android:pivotY="0%" + android:duration="@android:integer/config_shortAnimTime" /> + <alpha android:interpolator="@anim/decelerate_interpolator" + android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/grow_fade_in_center.xml b/core/res/res/anim/grow_fade_in_center.xml index 01d7a77..c07a82e 100644 --- a/core/res/res/anim/grow_fade_in_center.xml +++ b/core/res/res/anim/grow_fade_in_center.xml @@ -21,6 +21,9 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:fromXScale="0.6" android:toXScale="1.0" android:fromYScale="0.6" android:toYScale="1.0" - android:pivotX="50%" android:pivotY="50%" android:duration="125" /> - <alpha android:interpolator="@anim/decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" /> + android:pivotX="50%" android:pivotY="50%" + android:duration="@android:integer/config_shortAnimTime" /> + <alpha android:interpolator="@anim/decelerate_interpolator" + android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/grow_fade_in_from_bottom.xml b/core/res/res/anim/grow_fade_in_from_bottom.xml index d3e468d..848c677 100644 --- a/core/res/res/anim/grow_fade_in_from_bottom.xml +++ b/core/res/res/anim/grow_fade_in_from_bottom.xml @@ -21,6 +21,9 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:fromXScale="0.5" android:toXScale="1.0" android:fromYScale="0.5" android:toYScale="1.0" - android:pivotX="0%" android:pivotY="100%" android:duration="125" /> - <alpha android:interpolator="@anim/decelerate_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" /> + android:pivotX="0%" android:pivotY="100%" + android:duration="@android:integer/config_shortAnimTime" /> + <alpha android:interpolator="@anim/decelerate_interpolator" + android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/input_method_enter.xml b/core/res/res/anim/input_method_enter.xml index 6263c37..3858651 100644 --- a/core/res/res/anim/input_method_enter.xml +++ b/core/res/res/anim/input_method_enter.xml @@ -20,7 +20,8 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromYDelta="20%" android:toYDelta="0" android:duration="100"/> + <translate android:fromYDelta="20%" android:toYDelta="0" + android:duration="@android:integer/config_shortAnimTime"/> <alpha android:fromAlpha="0.5" android:toAlpha="1.0" - android:duration="100" /> + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml index af9382c..25369ab 100644 --- a/core/res/res/anim/input_method_exit.xml +++ b/core/res/res/anim/input_method_exit.xml @@ -19,7 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator"> - <translate android:fromYDelta="0" android:toYDelta="20%" android:duration="100"/> + <translate android:fromYDelta="0" android:toYDelta="20%" + android:duration="@android:integer/config_shortAnimTime"/> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="100"/> + android:duration="@android:integer/config_shortAnimTime"/> </set> diff --git a/core/res/res/anim/input_method_fancy_enter.xml b/core/res/res/anim/input_method_fancy_enter.xml index 15f5ad7..c6949b3 100644 --- a/core/res/res/anim/input_method_fancy_enter.xml +++ b/core/res/res/anim/input_method_fancy_enter.xml @@ -23,6 +23,7 @@ <scale android:fromXScale="2.0" android:toXScale="1.0" android:fromYScale="2.0" android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%" - android:duration="100" /> - <translate android:fromYDelta="100%" android:toYDelta="0" android:duration="100"/> + android:duration="@android:integer/config_shortAnimTime" /> + <translate android:fromYDelta="100%" android:toYDelta="0" + android:duration="@android:integer/config_shortAnimTime"/> </set> diff --git a/core/res/res/anim/input_method_fancy_exit.xml b/core/res/res/anim/input_method_fancy_exit.xml index ecc5de3..7333cca 100644 --- a/core/res/res/anim/input_method_fancy_exit.xml +++ b/core/res/res/anim/input_method_fancy_exit.xml @@ -22,6 +22,7 @@ <scale android:fromXScale="1.0" android:toXScale="2.0" android:fromYScale="1.0" android:toYScale="2.0" android:pivotX="50%" android:pivotY="50%" - android:duration="100" /> - <translate android:fromYDelta="0" android:toYDelta="100%" android:duration="100"/> + android:duration="@android:integer/config_shortAnimTime" /> + <translate android:fromYDelta="0" android:toYDelta="100%" + android:duration="@android:integer/config_shortAnimTime"/> </set> diff --git a/core/res/res/anim/lock_screen_exit.xml b/core/res/res/anim/lock_screen_exit.xml index 54d8e93..55c5ec9 100644 --- a/core/res/res/anim/lock_screen_exit.xml +++ b/core/res/res/anim/lock_screen_exit.xml @@ -19,5 +19,6 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator"> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/options_panel_enter.xml b/core/res/res/anim/options_panel_enter.xml index 9614014..d81866b 100644 --- a/core/res/res/anim/options_panel_enter.xml +++ b/core/res/res/anim/options_panel_enter.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromYDelta="25%" android:toYDelta="0" android:duration="75"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" /> + <translate android:fromYDelta="25%" android:toYDelta="0" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/options_panel_exit.xml b/core/res/res/anim/options_panel_exit.xml index c9bee1d..4e7af7a 100644 --- a/core/res/res/anim/options_panel_exit.xml +++ b/core/res/res/anim/options_panel_exit.xml @@ -19,7 +19,9 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator"> - <translate android:fromYDelta="0" android:toYDelta="50%" android:duration="50"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="50" /> + <translate android:fromYDelta="0" android:toYDelta="50%" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/push_down_in.xml b/core/res/res/anim/push_down_in.xml index 1cb1597..6ab9a04 100644 --- a/core/res/res/anim/push_down_in.xml +++ b/core/res/res/anim/push_down_in.xml @@ -15,6 +15,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromYDelta="-100%p" android:toYDelta="0" android:duration="300"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> + <translate android:fromYDelta="-100%p" android:toYDelta="0" + android:duration="@android:integer/config_longAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_longAnimTime" /> </set> diff --git a/core/res/res/anim/push_down_out.xml b/core/res/res/anim/push_down_out.xml index 1ad49b0..ce36458 100644 --- a/core/res/res/anim/push_down_out.xml +++ b/core/res/res/anim/push_down_out.xml @@ -15,6 +15,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromYDelta="0" android:toYDelta="100%p" android:duration="300"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="300" /> + <translate android:fromYDelta="0" android:toYDelta="100%p" + android:duration="@android:integer/config_longAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_longAnimTime" /> </set> diff --git a/core/res/res/anim/push_up_in.xml b/core/res/res/anim/push_up_in.xml index a2f47e9..6ef582c 100644 --- a/core/res/res/anim/push_up_in.xml +++ b/core/res/res/anim/push_up_in.xml @@ -15,6 +15,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromYDelta="100%p" android:toYDelta="0" android:duration="300"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> + <translate android:fromYDelta="100%p" android:toYDelta="0" + android:duration="@android:integer/config_longAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_longAnimTime" /> </set> diff --git a/core/res/res/anim/push_up_out.xml b/core/res/res/anim/push_up_out.xml index 2803435..2b267d5 100644 --- a/core/res/res/anim/push_up_out.xml +++ b/core/res/res/anim/push_up_out.xml @@ -15,6 +15,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromYDelta="0" android:toYDelta="-100%p" android:duration="300"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="300" /> + <translate android:fromYDelta="0" android:toYDelta="-100%p" + android:duration="@android:integer/config_longAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_longAnimTime" /> </set> diff --git a/core/res/res/anim/search_bar_enter.xml b/core/res/res/anim/search_bar_enter.xml index ecb86c1..c85caaa 100644 --- a/core/res/res/anim/search_bar_enter.xml +++ b/core/res/res/anim/search_bar_enter.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromYDelta="-25%" android:toYDelta="0" android:duration="75"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="75" /> + <translate android:fromYDelta="-25%" android:toYDelta="0" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/search_bar_exit.xml b/core/res/res/anim/search_bar_exit.xml index db7142e..1609a12 100644 --- a/core/res/res/anim/search_bar_exit.xml +++ b/core/res/res/anim/search_bar_exit.xml @@ -19,7 +19,9 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator"> - <translate android:fromYDelta="0" android:toYDelta="-50%" android:duration="50"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="50" /> + <translate android:fromYDelta="0" android:toYDelta="-50%" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/shrink_fade_out.xml b/core/res/res/anim/shrink_fade_out.xml index ae15ff9..4000c23 100644 --- a/core/res/res/anim/shrink_fade_out.xml +++ b/core/res/res/anim/shrink_fade_out.xml @@ -20,6 +20,9 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:fromXScale="1.0" android:toXScale="1.0" android:fromYScale="1.0" android:toYScale="0.3" - android:pivotX="0%" android:pivotY="0%" android:duration="125" /> - <alpha android:interpolator="@anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="75"/> + android:pivotX="0%" android:pivotY="0%" + android:duration="@android:integer/config_shortAnimTime" /> + <alpha android:interpolator="@anim/accelerate_interpolator" + android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_shortAnimTime"/> </set> diff --git a/core/res/res/anim/shrink_fade_out_center.xml b/core/res/res/anim/shrink_fade_out_center.xml index 7b0be34..a41731e 100644 --- a/core/res/res/anim/shrink_fade_out_center.xml +++ b/core/res/res/anim/shrink_fade_out_center.xml @@ -20,6 +20,9 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:fromXScale="1.0" android:toXScale="0.5" android:fromYScale="1.0" android:toYScale="0.5" - android:pivotX="50%" android:pivotY="50%" android:duration="125" /> - <alpha android:interpolator="@anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="75"/> + android:pivotX="50%" android:pivotY="50%" + android:duration="@android:integer/config_shortAnimTime" /> + <alpha android:interpolator="@anim/accelerate_interpolator" + android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_shortAnimTime"/> </set> diff --git a/core/res/res/anim/shrink_fade_out_from_bottom.xml b/core/res/res/anim/shrink_fade_out_from_bottom.xml index 61f29d5..345a2e0 100644 --- a/core/res/res/anim/shrink_fade_out_from_bottom.xml +++ b/core/res/res/anim/shrink_fade_out_from_bottom.xml @@ -20,6 +20,9 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:fromXScale="1.0" android:toXScale="1.0" android:fromYScale="1.0" android:toYScale="0.3" - android:pivotX="0%" android:pivotY="100%" android:duration="125" /> - <alpha android:interpolator="@anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="75"/> + android:pivotX="0%" android:pivotY="100%" + android:duration="@android:integer/config_shortAnimTime" /> + <alpha android:interpolator="@anim/accelerate_interpolator" + android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_shortAnimTime"/> </set> diff --git a/core/res/res/anim/slide_in_bottom.xml b/core/res/res/anim/slide_in_bottom.xml index 569d974..d111854 100644 --- a/core/res/res/anim/slide_in_bottom.xml +++ b/core/res/res/anim/slide_in_bottom.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromYDelta="50%p" android:toYDelta="0" android:duration="200"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" /> + <translate android:fromYDelta="50%p" android:toYDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/slide_in_child_bottom.xml b/core/res/res/anim/slide_in_child_bottom.xml index ce51c36..2ab0f66 100644 --- a/core/res/res/anim/slide_in_child_bottom.xml +++ b/core/res/res/anim/slide_in_child_bottom.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromYDelta="100%" android:toYDelta="0" android:duration="200"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" /> + <translate android:fromYDelta="100%" android:toYDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/slide_in_left.xml b/core/res/res/anim/slide_in_left.xml index 033bfe2..9a585f5 100644 --- a/core/res/res/anim/slide_in_left.xml +++ b/core/res/res/anim/slide_in_left.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromXDelta="-50%p" android:toXDelta="0" android:duration="200"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" /> + <translate android:fromXDelta="-50%p" android:toXDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/slide_in_right.xml b/core/res/res/anim/slide_in_right.xml index 76b336c..125060e 100644 --- a/core/res/res/anim/slide_in_right.xml +++ b/core/res/res/anim/slide_in_right.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromXDelta="50%p" android:toXDelta="0" android:duration="200"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" /> + <translate android:fromXDelta="50%p" android:toXDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/slide_in_top.xml b/core/res/res/anim/slide_in_top.xml index 15df913..f4810ab 100644 --- a/core/res/res/anim/slide_in_top.xml +++ b/core/res/res/anim/slide_in_top.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromYDelta="-50%p" android:toYDelta="0" android:duration="200"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="200" /> + <translate android:fromYDelta="-50%p" android:toYDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/slide_out_bottom.xml b/core/res/res/anim/slide_out_bottom.xml index 8f6e8b0..8ddcd59 100644 --- a/core/res/res/anim/slide_out_bottom.xml +++ b/core/res/res/anim/slide_out_bottom.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromYDelta="0" android:toYDelta="50%p" android:duration="200"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" /> + <translate android:fromYDelta="0" android:toYDelta="50%p" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/slide_out_left.xml b/core/res/res/anim/slide_out_left.xml index 979d10a..73337d4 100644 --- a/core/res/res/anim/slide_out_left.xml +++ b/core/res/res/anim/slide_out_left.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromXDelta="0" android:toXDelta="-50%p" android:duration="200"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" /> + <translate android:fromXDelta="0" android:toXDelta="-50%p" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/slide_out_right.xml b/core/res/res/anim/slide_out_right.xml index b5f9bb9..de8c512 100644 --- a/core/res/res/anim/slide_out_right.xml +++ b/core/res/res/anim/slide_out_right.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromXDelta="0" android:toXDelta="50%p" android:duration="200"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" /> + <translate android:fromXDelta="0" android:toXDelta="50%p" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/slide_out_top.xml b/core/res/res/anim/slide_out_top.xml index b15323e..3134a57 100644 --- a/core/res/res/anim/slide_out_top.xml +++ b/core/res/res/anim/slide_out_top.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromYDelta="0" android:toYDelta="-50%p" android:duration="200"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" /> + <translate android:fromYDelta="0" android:toYDelta="-50%p" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/status_bar_enter.xml b/core/res/res/anim/status_bar_enter.xml index b57d8e7..d308ad5 100644 --- a/core/res/res/anim/status_bar_enter.xml +++ b/core/res/res/anim/status_bar_enter.xml @@ -20,7 +20,7 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> <translate android:fromYDelta="-75%" android:toYDelta="0" - android:duration="200"/> + android:duration="@android:integer/config_mediumAnimTime"/> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="200" /> + android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/status_bar_exit.xml b/core/res/res/anim/status_bar_exit.xml index 8c6dc1c..43a1b9a 100644 --- a/core/res/res/anim/status_bar_exit.xml +++ b/core/res/res/anim/status_bar_exit.xml @@ -20,7 +20,7 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator"> <translate android:fromYDelta="0" android:toYDelta="-75%" - android:startOffset="100" android:duration="200"/> + android:startOffset="100" android:duration="@android:integer/config_mediumAnimTime"/> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:startOffset="100" android:duration="200" /> + android:startOffset="100" android:duration="@android:integer/config_mediumAnimTime" /> </set> diff --git a/core/res/res/anim/submenu_enter.xml b/core/res/res/anim/submenu_enter.xml index 32a8054..5a94971 100644 --- a/core/res/res/anim/submenu_enter.xml +++ b/core/res/res/anim/submenu_enter.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromXDelta="-25%" android:toXDelta="0" android:duration="100"/> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="100" /> + <translate android:fromXDelta="-25%" android:toXDelta="0" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/submenu_exit.xml b/core/res/res/anim/submenu_exit.xml index 9283751..20fbefc 100644 --- a/core/res/res/anim/submenu_exit.xml +++ b/core/res/res/anim/submenu_exit.xml @@ -19,6 +19,8 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> - <translate android:fromXDelta="0" android:toXDelta="25%" android:duration="50"/> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="50" /> + <translate android:fromXDelta="0" android:toXDelta="25%" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml index 6bef7a4..c289869 100644 --- a/core/res/res/anim/task_close_enter.xml +++ b/core/res/res/anim/task_close_enter.xml @@ -21,5 +21,6 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator" android:zAdjustment="top"> - <translate android:fromXDelta="-100%" android:toXDelta="0" android:duration="200"/> + <translate android:fromXDelta="-100%" android:toXDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> </set> diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml index 48dc31a..96fff94 100644 --- a/core/res/res/anim/task_close_exit.xml +++ b/core/res/res/anim/task_close_exit.xml @@ -20,5 +20,6 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromXDelta="0%" android:toXDelta="33%" android:duration="200"/> + <translate android:fromXDelta="0%" android:toXDelta="33%" + android:duration="@android:integer/config_mediumAnimTime"/> </set> diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml index e498c9d..8e7d049 100644 --- a/core/res/res/anim/task_open_enter.xml +++ b/core/res/res/anim/task_open_enter.xml @@ -20,5 +20,6 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromXDelta="33%" android:toXDelta="0" android:duration="200"/> + <translate android:fromXDelta="33%" android:toXDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> </set> diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml index 8ef4006..5e5c66d 100644 --- a/core/res/res/anim/task_open_exit.xml +++ b/core/res/res/anim/task_open_exit.xml @@ -21,5 +21,6 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator" android:zAdjustment="top"> - <translate android:fromXDelta="0%" android:toXDelta="-100%" android:duration="200"/> + <translate android:fromXDelta="0%" android:toXDelta="-100%" + android:duration="@android:integer/config_mediumAnimTime"/> </set> diff --git a/core/res/res/anim/toast_enter.xml b/core/res/res/anim/toast_enter.xml index 83cb1fe..57310d9 100644 --- a/core/res/res/anim/toast_enter.xml +++ b/core/res/res/anim/toast_enter.xml @@ -20,4 +20,5 @@ <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/decelerate_interpolator" - android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="400" /> + android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_longAnimTime" /> diff --git a/core/res/res/anim/toast_exit.xml b/core/res/res/anim/toast_exit.xml index f922085..b7c5fa0 100644 --- a/core/res/res/anim/toast_exit.xml +++ b/core/res/res/anim/toast_exit.xml @@ -20,5 +20,6 @@ <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator" - android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="400" + android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_longAnimTime" /> diff --git a/core/res/res/drawable-land/statusbar_background.png b/core/res/res/drawable-land/statusbar_background.png Binary files differindex bafa5c5..2a351a5 100644 --- a/core/res/res/drawable-land/statusbar_background.png +++ b/core/res/res/drawable-land/statusbar_background.png diff --git a/core/res/res/drawable/scrollbar_handle_horizontal.9.png b/core/res/res/drawable/scrollbar_handle_horizontal.9.png Binary files differindex 324b4bd..8584d1f 100755 --- a/core/res/res/drawable/scrollbar_handle_horizontal.9.png +++ b/core/res/res/drawable/scrollbar_handle_horizontal.9.png diff --git a/core/res/res/drawable/scrollbar_handle_vertical.9.png b/core/res/res/drawable/scrollbar_handle_vertical.9.png Binary files differindex 3519d68..331a05d 100755 --- a/core/res/res/drawable/scrollbar_handle_vertical.9.png +++ b/core/res/res/drawable/scrollbar_handle_vertical.9.png diff --git a/core/res/res/drawable/statusbar_background.png b/core/res/res/drawable/statusbar_background.png Binary files differindex 25a2344..2d8aa7a 100644 --- a/core/res/res/drawable/statusbar_background.png +++ b/core/res/res/drawable/statusbar_background.png diff --git a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml index 19305c5..881652b 100644 --- a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml +++ b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml @@ -20,7 +20,7 @@ android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:background="#A0000000" + android:background="@android:color/background_dark" > <!-- displays dots as user enters pin --> @@ -49,7 +49,6 @@ /> <ImageButton android:id="@+id/backspace" - style="@android:style/Widget.Button.Inset" android:src="@android:drawable/ic_input_delete" android:layout_width="wrap_content" android:layout_height="fill_parent" diff --git a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml index 6a05488..fc82e3f 100644 --- a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml +++ b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml @@ -16,231 +16,251 @@ ** limitations under the License. */ --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:background="#A0000000" + android:background="@android:color/background_dark" > - <!-- header text ('Enter Pin Code') --> - <TextView android:id="@+id/headerText" + <LinearLayout android:id="@+id/topDisplayGroup" android:layout_width="fill_parent" - android:layout_height="0dip" - android:layout_weight="1" - android:gravity="center" - android:textAppearance="?android:attr/textAppearanceLarge" - android:textSize="26sp" - /> + android:layout_height="115dip" + android:layout_alignParentTop="true" + android:orientation="vertical" + > + + <!-- header text ('Enter Pin Code') --> + <TextView android:id="@+id/headerText" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="9dip" + android:gravity="center" + android:textAppearance="?android:attr/textAppearanceLarge" + /> + + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="18dip" + android:layout_marginRight="6dip" + android:layout_marginLeft="6dip" + android:background="@android:drawable/edit_text" + > + + <!-- displays dots as user enters pin --> + <TextView android:id="@+id/pinDisplay" + android:layout_width="wrap_content" + android:layout_height="64dip" + android:layout_centerInParent="true" + android:maxLines="1" + android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:textStyle="bold" + android:inputType="textPassword" + /> + + <ImageButton android:id="@+id/backspace" + android:src="@android:drawable/ic_input_delete" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:layout_marginRight="1dip" + /> + </RelativeLayout> + - <!-- displays dots as user enters pin --> - <LinearLayout - android:orientation="horizontal" + </LinearLayout> + + <!-- Keypad section --> + <LinearLayout android:id="@+id/keyPad" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:addStatesFromChildren="true" - android:gravity="center_vertical" - android:baselineAligned="false" - android:paddingRight="0dip" - android:layout_marginTop="8dip" - android:layout_marginBottom="5dip" - android:layout_marginLeft="5dip" - android:background="@android:drawable/edit_text" - > - - <EditText android:id="@+id/pinDisplay" - android:layout_width="0dip" - android:layout_weight="1" - android:layout_height="fill_parent" - android:maxLines="1" - android:background="@null" - android:inputType="textPassword" + android:layout_below="@id/topDisplayGroup" + android:orientation="vertical" + > + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="64dip" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:orientation="horizontal" + > + + <Button android:id="@+id/one" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" /> - <ImageButton android:id="@+id/backspace" - style="@android:style/Widget.Button.Inset" - android:src="@android:drawable/ic_input_delete" - android:layout_width="wrap_content" - android:layout_height="fill_parent" - android:layout_marginTop="2dip" - android:layout_marginRight="2dip" - android:layout_marginBottom="2dip" - android:gravity="center" + <Button android:id="@+id/two" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" /> - </LinearLayout> + <Button android:id="@+id/three" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" + /> + </LinearLayout> - <!-- Keypad section --> - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="0sp" - android:layout_weight="1" - android:layout_marginLeft="5dip" - android:layout_marginRight="4dip" - android:orientation="horizontal" - > - - <Button android:id="@+id/one" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="64dip" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:orientation="horizontal" + > - <Button android:id="@+id/two" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> + <Button android:id="@+id/four" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" + /> - <Button android:id="@+id/three" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> - </LinearLayout> + <Button android:id="@+id/five" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" + /> - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="0sp" - android:layout_weight="1" - android:layout_marginLeft="5dip" - android:layout_marginRight="4dip" - android:orientation="horizontal" - > - - <Button android:id="@+id/four" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> + <Button android:id="@+id/six" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" + /> + </LinearLayout> - <Button android:id="@+id/five" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="64dip" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:orientation="horizontal" + > - <Button android:id="@+id/six" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> - </LinearLayout> + <Button android:id="@+id/seven" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" + /> - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="0sp" - android:layout_weight="1" - android:layout_marginLeft="5dip" - android:layout_marginRight="4dip" - android:orientation="horizontal" - > - - <Button android:id="@+id/seven" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> + <Button android:id="@+id/eight" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" + /> - <Button android:id="@+id/eight" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> + <Button android:id="@+id/nine" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" + /> + </LinearLayout> - <Button android:id="@+id/nine" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> - </LinearLayout> + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="64dip" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:orientation="horizontal" + > - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="0sp" - android:layout_weight="1" - android:layout_marginLeft="5dip" - android:layout_marginRight="4dip" - android:orientation="horizontal" - > - - <Button android:id="@+id/ok" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="18sp" - android:text="@android:string/ok" - /> + <Button android:id="@+id/ok" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + android:text="@android:string/ok" + /> - <Button android:id="@+id/zero" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="22sp" - /> + <Button android:id="@+id/zero" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textStyle="bold" + /> - <Button android:id="@+id/cancel" - android:layout_width="0sp" - android:layout_height="fill_parent" - android:layout_weight="1" - android:paddingLeft="4dip" - style="?android:attr/buttonStyleSmall" - android:textSize="18sp" - android:text="@android:string/cancel" - /> + <Button android:id="@+id/cancel" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginLeft="2dip" + android:layout_marginRight="2dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + android:text="@android:string/cancel" + /> + </LinearLayout> + + <!-- end keypad --> </LinearLayout> <!-- emergency call button --> - <RelativeLayout - android:layout_width="fill_parent" - android:layout_height="0dip" - android:layout_weight="1" - > + <Button + android:id="@+id/emergencyCall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_alignParentBottom="true" + android:drawableLeft="@android:drawable/ic_emergency" + android:drawablePadding="3dip" + android:text="@android:string/lockscreen_emergency_call" + /> - <Button - android:id="@+id/emergencyCall" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerInParent="true" - android:drawableLeft="@drawable/ic_emergency" - android:drawablePadding="3dip" - android:paddingTop="4dip" - android:paddingBottom="4dip" - android:text="@android:string/lockscreen_emergency_call" - /> + <!-- spacer below keypad --> + <View + android:id="@+id/spacerBottom" + android:layout_width="fill_parent" + android:layout_height="1dip" + android:layout_marginBottom="6dip" + android:layout_above="@id/emergencyCall" + android:background="@android:drawable/divider_horizontal_dark"/> - </RelativeLayout> -</LinearLayout> +</RelativeLayout> diff --git a/core/res/res/layout/select_dialog_singlechoice.xml b/core/res/res/layout/select_dialog_singlechoice.xml index ec97d7b..3e07f23 100644 --- a/core/res/res/layout/select_dialog_singlechoice.xml +++ b/core/res/res/layout/select_dialog_singlechoice.xml @@ -19,7 +19,8 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" - android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textColor="@android:color/bright_foreground_light" android:gravity="center_vertical" android:paddingLeft="12dip" android:paddingRight="7dip" diff --git a/core/res/res/layout/zoom_container.xml b/core/res/res/layout/zoom_container.xml index fc5cf3d..52bc635 100644 --- a/core/res/res/layout/zoom_container.xml +++ b/core/res/res/layout/zoom_container.xml @@ -19,10 +19,11 @@ --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ZoomControls android:id="@+id/zoomControls" - android:layout_gravity="bottom|center_horizontal" + android:layout_gravity="center_horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/ZoomControls" + android:gravity="center_horizontal" android:background="@null" /> </merge> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 3fb7e86..8150067 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -19,7 +19,7 @@ --> <resources> <drawable name="screen_background_light">#ffffffff</drawable> - <drawable name="screen_background_dark">#ff262626</drawable> + <drawable name="screen_background_dark">#ff1a1a1a</drawable> <drawable name="status_bar_closed_default_background">#ff000000</drawable> <drawable name="status_bar_opened_default_background">#ff000000</drawable> <drawable name="search_bar_default_color">#ff000000</drawable> @@ -28,7 +28,7 @@ <color name="white">#ffffffff</color> <color name="black">#ff000000</color> <color name="transparent">#00000000</color> - <color name="background_dark">#ff262626</color> + <color name="background_dark">#ff1a1a1a</color> <color name="bright_foreground_dark">#ffffffff</color> <color name="bright_foreground_dark_disabled">#80ffffff</color> <color name="bright_foreground_dark_inverse">#ff000000</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f370151..83ac8e2 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -25,4 +25,13 @@ manager will disable alpha trasformation in animations where not strictly needed. --> <bool name="config_sf_limitedAlpha">false</bool> + + <!-- The duration (in milliseconds) of a short animation. --> + <integer name="config_shortAnimTime">100</integer> + + <!-- The duration (in milliseconds) of a medium-length animation. --> + <integer name="config_mediumAnimTime">150</integer> + + <!-- The duration (in milliseconds) of a long animation. --> + <integer name="config_longAnimTime">300</integer> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 0603fa3..88c7b35 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1095,4 +1095,8 @@ <!-- Drawable to use as a background for a taller version of the titlebar --> <public type="drawable" name="title_bar_tall" id="0x010800b7" /> + + <public type="integer" name="config_shortAnimTime" id="0x010e0000" /> + <public type="integer" name="config_mediumAnimTime" id="0x010e0001" /> + <public type="integer" name="config_longAnimTime" id="0x010e0002" /> </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index ff8f8c2..a152410 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -176,7 +176,7 @@ <item name="android:focusable">true</item> <item name="android:clickable">true</item> <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item> - <item name="android:textColor">@android:color/primary_text_light_nodisable</item> + <item name="android:textColor">@android:color/primary_text_light</item> <item name="android:gravity">center_vertical|center_horizontal</item> </style> diff --git a/include/media/IMediaRecorder.h b/include/media/IMediaRecorder.h index eace996..64d3a40 100644 --- a/include/media/IMediaRecorder.h +++ b/include/media/IMediaRecorder.h @@ -42,6 +42,7 @@ public: virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0; virtual status_t setVideoSize(int width, int height) = 0; virtual status_t setVideoFrameRate(int frames_per_second) = 0; + virtual status_t setParameters(const String8& params) = 0; virtual status_t setListener(const sp<IMediaPlayerClient>& listener) = 0; virtual status_t prepare() = 0; virtual status_t getMaxAmplitude(int* max) = 0; diff --git a/include/media/PVMediaRecorder.h b/include/media/PVMediaRecorder.h index 3315c59..0c71932 100644 --- a/include/media/PVMediaRecorder.h +++ b/include/media/PVMediaRecorder.h @@ -45,6 +45,7 @@ public: status_t setPreviewSurface(const sp<ISurface>& surface); status_t setOutputFile(const char *path); status_t setOutputFile(int fd, int64_t offset, int64_t length); + status_t setParameters(const String8& params); status_t setListener(const sp<IMediaPlayerClient>& listener); status_t prepare(); status_t start(); diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h index 8991f08..78d7621 100644 --- a/include/media/mediarecorder.h +++ b/include/media/mediarecorder.h @@ -89,14 +89,24 @@ enum media_recorder_states { }; // The "msg" code passed to the listener in notify. -enum { - MEDIA_RECORDER_EVENT_ERROR = 1 +enum media_recorder_event_type { + MEDIA_RECORDER_EVENT_ERROR = 1, + MEDIA_RECORDER_EVENT_INFO = 2 }; -enum { +enum media_recorder_error_type { MEDIA_RECORDER_ERROR_UNKNOWN = 1 }; +// The codes are distributed as follow: +// 0xx: Reserved +// 8xx: General info/warning +// +enum media_recorder_info_type { + MEDIA_RECORDER_INFO_UNKNOWN = 1, + MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800 +}; + // ---------------------------------------------------------------------------- // ref-counted object for callbacks class MediaRecorderListener: virtual public RefBase @@ -123,6 +133,7 @@ public: status_t setOutputFile(int fd, int64_t offset, int64_t length); status_t setVideoSize(int width, int height); status_t setVideoFrameRate(int frames_per_second); + status_t setParameters(const String8& params); status_t setListener(const sp<MediaRecorderListener>& listener); status_t prepare(); status_t getMaxAmplitude(int* max); diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp index 57a53bd..5652b28 100644 --- a/libs/audioflinger/AudioFlinger.cpp +++ b/libs/audioflinger/AudioFlinger.cpp @@ -837,12 +837,12 @@ void AudioFlinger::handleForcedSpeakerRoute(int command) } #ifdef WITH_A2DP -void AudioFlinger::handleStreamDisablesA2dp(int command) +// handleStreamDisablesA2dp_l() must be called with AudioFlinger::mLock held +void AudioFlinger::handleStreamDisablesA2dp_l(int command) { switch(command) { case ACTIVE_TRACK_ADDED: { - AutoMutex lock(mHardwareLock); if (mA2dpDisableCount++ == 0) { if (mA2dpEnabled) { setA2dpEnabled_l(false); @@ -854,8 +854,7 @@ void AudioFlinger::handleStreamDisablesA2dp(int command) break; case ACTIVE_TRACK_REMOVED: { - AutoMutex lock(mHardwareLock); - if (mA2dpDisableCount > 0){ + if (mA2dpDisableCount > 0) { if (--mA2dpDisableCount == 0) { if (mA2dpSuppressed) { setA2dpEnabled_l(true); @@ -1502,8 +1501,10 @@ void AudioFlinger::MixerThread::addActiveTrack_l(const wp<Track>& t) mAudioFlinger->handleForcedSpeakerRoute(ACTIVE_TRACK_ADDED); } #ifdef WITH_A2DP + // AudioFlinger::mLock must be locked before calling + // handleStreamDisablesA2dp_l because it calls setA2dpEnabled_l(). if (streamDisablesA2dp(track->type())) { - mAudioFlinger->handleStreamDisablesA2dp(ACTIVE_TRACK_ADDED); + mAudioFlinger->handleStreamDisablesA2dp_l(ACTIVE_TRACK_ADDED); } #endif } @@ -1524,8 +1525,10 @@ void AudioFlinger::MixerThread::removeActiveTrack_l(const wp<Track>& t) mAudioFlinger->handleForcedSpeakerRoute(ACTIVE_TRACK_REMOVED); } #ifdef WITH_A2DP + // AudioFlinger::mLock must be locked before calling + // handleStreamDisablesA2dp_l because it calls setA2dpEnabled_l(). if (streamDisablesA2dp(track->type())) { - mAudioFlinger->handleStreamDisablesA2dp(ACTIVE_TRACK_REMOVED); + mAudioFlinger->handleStreamDisablesA2dp_l(ACTIVE_TRACK_REMOVED); } #endif } @@ -2476,8 +2479,15 @@ status_t AudioFlinger::AudioRecordThread::start(MixerThread::RecordTrack* record mRecordTrack = recordTrack; #ifdef WITH_A2DP - if (streamDisablesA2dp(recordTrack->type())) { - mAudioFlinger->handleStreamDisablesA2dp(ACTIVE_TRACK_ADDED); + { // scope for lock2 + + // AudioFlinger::mLock must be locked before calling + // handleStreamDisablesA2dp_l because it calls setA2dpEnabled_l(). + AutoMutex lock2(&mAudioFlinger->mLock); + + // Currently there is no way to detect if we are recording over SCO, + // so we disable A2DP during any recording. + mAudioFlinger->handleStreamDisablesA2dp_l(ACTIVE_TRACK_ADDED); } #endif @@ -2494,8 +2504,15 @@ void AudioFlinger::AudioRecordThread::stop(MixerThread::RecordTrack* recordTrack AutoMutex lock(&mLock); if (mActive && (recordTrack == mRecordTrack.get())) { #ifdef WITH_A2DP - if (streamDisablesA2dp(recordTrack->type())) { - mAudioFlinger->handleStreamDisablesA2dp(ACTIVE_TRACK_REMOVED); + { // scope for lock2 + + // AudioFlinger::mLock must be locked before calling + // handleStreamDisablesA2dp_l because it calls setA2dpEnabled_l(). + AutoMutex lock2(&mAudioFlinger->mLock); + + // Currently there is no way to detect if we are recording over SCO, + // so we disable A2DP during any recording. + mAudioFlinger->handleStreamDisablesA2dp_l(ACTIVE_TRACK_REMOVED); } #endif mActive = false; diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h index 596e7f3..ab15947 100644 --- a/libs/audioflinger/AudioFlinger.h +++ b/libs/audioflinger/AudioFlinger.h @@ -176,7 +176,7 @@ private: }; void handleForcedSpeakerRoute(int command); #ifdef WITH_A2DP - void handleStreamDisablesA2dp(int command); + void handleStreamDisablesA2dp_l(int command); #endif // Internal dump utilites. diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 4906cbb..1c08cba 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -71,6 +71,7 @@ public class MediaRecorder private FileDescriptor mFd; private EventHandler mEventHandler; private OnErrorListener mOnErrorListener; + private OnInfoListener mOnInfoListener; /** * Default constructor. @@ -256,6 +257,15 @@ public class MediaRecorder public native void setVideoFrameRate(int rate) throws IllegalStateException; /** + * Sets the maximum duration (in ms) of the recording session. + * Call this after setOutFormat() but before prepare(). + * + * @param max_duration_ms the maximum duration in ms (if zero or negative, disables the duration limit) + * + */ + public native void setMaxDuration(int max_duration_ms) throws IllegalArgumentException; + + /** * Sets the audio encoder to be used for recording. If this method is not * called, the output file will not contain an audio track. Call this after * setOutputFormat() but before prepare(). @@ -420,6 +430,48 @@ public class MediaRecorder mOnErrorListener = l; } + /* Do not change these values without updating their counterparts + * in include/media/mediarecorder.h! + */ + /** Unspecified media recorder error. + * @see android.media.MediaRecorder.OnInfoListener + */ + public static final int MEDIA_RECORDER_INFO_UNKNOWN = 1; + /** A maximum duration had been setup and has now been reached. + * @see android.media.MediaRecorder.OnInfoListener + */ + public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800; + + /** + * Interface definition for a callback to be invoked when an error + * occurs while recording. + */ + public interface OnInfoListener + { + /** + * Called when an error occurs while recording. + * + * @param mr the MediaRecorder that encountered the error + * @param what the type of error that has occurred: + * <ul> + * <li>{@link #MEDIA_RECORDER_INFO_UNKNOWN} + * </ul> + * @param extra an extra code, specific to the error type + */ + void onInfo(MediaRecorder mr, int what, int extra); + } + + /** + * Register a callback to be invoked when an informational event occurs while + * recording. + * + * @param listener the callback that will be run + */ + public void setOnInfoListener(OnInfoListener listener) + { + mOnInfoListener = listener; + } + private class EventHandler extends Handler { private MediaRecorder mMediaRecorder; @@ -429,10 +481,11 @@ public class MediaRecorder mMediaRecorder = mr; } - /* Do not change this value without updating its counterpart + /* Do not change these values without updating their counterparts * in include/media/mediarecorder.h! */ private static final int MEDIA_RECORDER_EVENT_ERROR = 1; + private static final int MEDIA_RECORDER_EVENT_INFO = 2; @Override public void handleMessage(Message msg) { @@ -447,6 +500,12 @@ public class MediaRecorder return; + case MEDIA_RECORDER_EVENT_INFO: + if (mOnInfoListener != null) + mOnInfoListener.onInfo(mMediaRecorder, msg.arg1, msg.arg2); + + return; + default: Log.e(TAG, "Unknown message type " + msg.what); return; diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 5f6f754..1e508d2 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -256,6 +256,18 @@ android_media_MediaRecorder_setVideoFrameRate(JNIEnv *env, jobject thiz, jint ra } static void +android_media_MediaRecorder_setMaxDuration(JNIEnv *env, jobject thiz, jint max_duration_ms) +{ + LOGV("setMaxDuration(%d)", max_duration_ms); + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + + char params[64]; + sprintf(params, "max-duration=%d", max_duration_ms); + + process_media_recorder_call(env, mr->setParameters(String8(params)), "java/lang/RuntimeException", "setMaxDuration failed."); +} + +static void android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz) { LOGV("prepare"); @@ -357,6 +369,7 @@ static JNINativeMethod gMethods[] = { {"_setOutputFile", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaRecorder_setOutputFileFD}, {"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize}, {"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate}, + {"setMaxDuration", "(I)V", (void *)android_media_MediaRecorder_setMaxDuration}, {"_prepare", "()V", (void *)android_media_MediaRecorder_prepare}, {"getMaxAmplitude", "()I", (void *)android_media_MediaRecorder_native_getMaxAmplitude}, {"start", "()V", (void *)android_media_MediaRecorder_start}, diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp index f187bf5..84d08c4 100644 --- a/media/libmedia/IMediaRecorder.cpp +++ b/media/libmedia/IMediaRecorder.cpp @@ -44,6 +44,7 @@ enum { SET_OUTPUT_FILE_FD, SET_VIDEO_SIZE, SET_VIDEO_FRAMERATE, + SET_PARAMETERS, SET_PREVIEW_SURFACE, SET_CAMERA, SET_LISTENER @@ -178,6 +179,16 @@ public: return reply.readInt32(); } + status_t setParameters(const String8& params) + { + LOGV("setParameter(%s)", params.string()); + Parcel data, reply; + data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor()); + data.writeString8(params); + remote()->transact(SET_PARAMETERS, data, &reply); + return reply.readInt32(); + } + status_t setListener(const sp<IMediaPlayerClient>& listener) { LOGV("setListener(%p)", listener.get()); @@ -385,6 +396,12 @@ status_t BnMediaRecorder::onTransact( reply->writeInt32(setVideoFrameRate(frames_per_second)); return NO_ERROR; } break; + case SET_PARAMETERS: { + LOGV("SET_PARAMETER"); + CHECK_INTERFACE(IMediaRecorder, data, reply); + reply->writeInt32(setParameters(data.readString8())); + return NO_ERROR; + } break; case SET_LISTENER: { LOGV("SET_LISTENER"); CHECK_INTERFACE(IMediaRecorder, data, reply); diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp index 572b1e7..23b3b9d 100644 --- a/media/libmedia/mediarecorder.cpp +++ b/media/libmedia/mediarecorder.cpp @@ -21,6 +21,7 @@ #include <ui/Surface.h> #include <media/mediarecorder.h> #include <utils/IServiceManager.h> +#include <utils/String8.h> #include <media/IMediaPlayerService.h> #include <media/IMediaRecorder.h> @@ -356,6 +357,23 @@ status_t MediaRecorder::setVideoFrameRate(int frames_per_second) return ret; } +status_t MediaRecorder::setParameters(const String8& params) { + LOGV("setParameters(%s)", params.string()); + if(mMediaRecorder == NULL) { + LOGE("media recorder is not initialized yet"); + return INVALID_OPERATION; + } + + status_t ret = mMediaRecorder->setParameters(params); + if (OK != ret) { + LOGE("setParameters(%s) failed: %d", params.string(), ret); + mCurrentState = MEDIA_RECORDER_ERROR; + return ret; + } + + return ret; +} + status_t MediaRecorder::prepare() { LOGV("prepare"); diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp index 4b45acb..5d1887d 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.cpp +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -154,6 +154,16 @@ status_t MediaRecorderClient::setVideoFrameRate(int frames_per_second) return mRecorder->setVideoFrameRate(frames_per_second); } +status_t MediaRecorderClient::setParameters(const String8& params) { + LOGV("setParameters(%s)", params.string()); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + LOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setParameters(params); +} + status_t MediaRecorderClient::prepare() { LOGV("prepare"); diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h index 93fd802..6a1c2d5 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.h +++ b/media/libmediaplayerservice/MediaRecorderClient.h @@ -39,6 +39,7 @@ public: virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); virtual status_t setVideoSize(int width, int height); virtual status_t setVideoFrameRate(int frames_per_second); + virtual status_t setParameters(const String8& params); virtual status_t setListener(const sp<IMediaPlayerClient>& listener); virtual status_t prepare(); virtual status_t getMaxAmplitude(int* max); 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 24edb65..05ac408 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioTrackTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioTrackTest.java @@ -22,7 +22,6 @@ 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; import android.util.Log; import android.test.suitebuilder.annotation.LargeTest; @@ -595,12 +594,12 @@ public class MediaAudioTrackTest extends ActivityInstrumentationTestCase2<MediaF //-------- tear down -------------- track.release(); } -/* - //Test case 7: setPlaybackRate() clips values over twice the output sample rate + + //Test case 7: setPlaybackRate() and retrieve value, should be the same for half the content SR @LargeTest - public void testSetPlaybackRateClip() throws Exception { + public void testSetGetPlaybackRate() throws Exception { // constants for test - final String TEST_NAME = "testSetPlaybackRateClip"; + final String TEST_NAME = "testSetGetPlaybackRate"; final int TEST_SR = 22050; final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; @@ -612,18 +611,17 @@ public class MediaAudioTrackTest extends ActivityInstrumentationTestCase2<MediaF AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE); byte data[] = new byte[minBuffSize/2]; - int outputSR = AudioTrack.getNativeOutputSampleRate(TEST_STREAM_TYPE); //-------- test -------------- track.write(data, 0, data.length); track.write(data, 0, data.length); assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); track.play(); - track.setPlaybackRate(3*outputSR); - assertTrue(TEST_NAME, track.getSampleRate() == 2*outputSR); + track.setPlaybackRate((int)(TEST_SR/2)); + assertTrue(TEST_NAME, track.getPlaybackRate() == (int)(TEST_SR/2)); //-------- tear down -------------- track.release(); } -*/ + //Test case 8: setPlaybackRate() invalid operation if track not initialized @LargeTest public void testSetPlaybackRateUninit() throws Exception { @@ -641,7 +639,8 @@ public class MediaAudioTrackTest extends ActivityInstrumentationTestCase2<MediaF minBuffSize, TEST_MODE); //-------- test -------------- assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA); - assertTrue(TEST_NAME, track.setPlaybackRate(TEST_SR/2) == AudioTrack.ERROR_INVALID_OPERATION); + assertTrue(TEST_NAME, + track.setPlaybackRate(TEST_SR/2) == AudioTrack.ERROR_INVALID_OPERATION); //-------- tear down -------------- track.release(); } @@ -863,8 +862,7 @@ public class MediaAudioTrackTest extends ActivityInstrumentationTestCase2<MediaF //-------- tear down -------------- track.release(); } -/* - //Test case 7: setLoopPoints() fails with start beyond what can be written for the track + //Test case 8: setLoopPoints() fails with start beyond what can be written for the track @LargeTest public void testSetLoopPointsStartTooFar() throws Exception { // constants for test @@ -891,7 +889,327 @@ public class MediaAudioTrackTest extends ActivityInstrumentationTestCase2<MediaF //-------- tear down -------------- track.release(); } -*/ + + //Test case 9: setLoopPoints() fails with end beyond what can be written for the track + @LargeTest + public void testSetLoopPointsEndTooFar() throws Exception { + // constants for test + final String TEST_NAME = "testSetLoopPointsEndTooFar"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STATIC; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + minBuffSize, TEST_MODE); + byte data[] = new byte[minBuffSize]; + int dataSizeInFrames = minBuffSize/2;//16bit data + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA); + track.write(data, 0, data.length); + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.setLoopPoints(dataSizeInFrames-10, dataSizeInFrames+50, 2) + == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + + //----------------------------------------------------------------- + // Audio data supply + //---------------------------------- + + //Test case 1: write() fails when supplying less data (bytes) than declared + @LargeTest + public void testWriteByteOffsetTooBig() throws Exception { + // constants for test + final String TEST_NAME = "testWriteByteOffsetTooBig"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + byte data[] = new byte[minBuffSize]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 10, data.length) == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + //Test case 2: write() fails when supplying less data (shorts) than declared + @LargeTest + public void testWriteShortOffsetTooBig() throws Exception { + // constants for test + final String TEST_NAME = "testWriteShortOffsetTooBig"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + short data[] = new short[minBuffSize/2]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 10, data.length) == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + //Test case 3: write() fails when supplying less data (bytes) than declared + @LargeTest + public void testWriteByteSizeTooBig() throws Exception { + // constants for test + final String TEST_NAME = "testWriteByteSizeTooBig"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + byte data[] = new byte[minBuffSize]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 0, data.length + 10) == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + //Test case 4: write() fails when supplying less data (shorts) than declared + @LargeTest + public void testWriteShortSizeTooBig() throws Exception { + // constants for test + final String TEST_NAME = "testWriteShortSizeTooBig"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + short data[] = new short[minBuffSize/2]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 0, data.length + 10) == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + //Test case 5: write() fails with negative offset + @LargeTest + public void testWriteByteNegativeOffset() throws Exception { + // constants for test + final String TEST_NAME = "testWriteByteNegativeOffset"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + byte data[] = new byte[minBuffSize]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, -10, data.length - 10) == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + //Test case 6: write() fails with negative offset + @LargeTest + public void testWriteShortNegativeOffset() throws Exception { + // constants for test + final String TEST_NAME = "testWriteShortNegativeOffset"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + short data[] = new short[minBuffSize/2]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, -10, data.length - 10) == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + //Test case 7: write() fails with negative size + @LargeTest + public void testWriteByteNegativeSize() throws Exception { + // constants for test + final String TEST_NAME = "testWriteByteNegativeSize"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + byte data[] = new byte[minBuffSize]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 0, -10) == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + //Test case 8: write() fails with negative size + @LargeTest + public void testWriteShortNegativeSize() throws Exception { + // constants for test + final String TEST_NAME = "testWriteShortNegativeSize"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + short data[] = new short[minBuffSize/2]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 0, -10) == AudioTrack.ERROR_BAD_VALUE); + //-------- tear down -------------- + track.release(); + } + + //Test case 9: write() succeeds and returns the size that was written for 16bit + @LargeTest + public void testWriteByte() throws Exception { + // constants for test + final String TEST_NAME = "testWriteByte"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + byte data[] = new byte[minBuffSize]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 0, data.length) == data.length); + //-------- tear down -------------- + track.release(); + } + + //Test case 10: write() succeeds and returns the size that was written for 16bit + @LargeTest + public void testWriteShort() throws Exception { + // constants for test + final String TEST_NAME = "testWriteShort"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + short data[] = new short[minBuffSize/2]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 0, data.length) == data.length); + //-------- tear down -------------- + track.release(); + } + + //Test case 11: write() succeeds and returns the size that was written for 8bit + @LargeTest + public void testWriteByte8bit() throws Exception { + // constants for test + final String TEST_NAME = "testWriteByte8bit"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + byte data[] = new byte[minBuffSize]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 0, data.length) == data.length); + //-------- tear down -------------- + track.release(); + } + + //Test case 12: write() succeeds and returns the size that was written for 8bit + @LargeTest + public void testWriteShort8bit() throws Exception { + // constants for test + final String TEST_NAME = "testWriteShort8bit"; + final int TEST_SR = 22050; + final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; + final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; + final int TEST_MODE = AudioTrack.MODE_STREAM; + final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; + + //-------- initialization -------------- + int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); + AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, + 2*minBuffSize, TEST_MODE); + short data[] = new short[minBuffSize/2]; + //-------- test -------------- + assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); + assertTrue(TEST_NAME, + track.write(data, 0, data.length) == data.length); + //-------- tear down -------------- + track.release(); + } } diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 72d88e2..275ff3a 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -26,7 +26,7 @@ <bool name="def_accelerometer_rotation">true</bool> <!-- Default screen brightness, from 0 to 255. 102 is 40%. --> <integer name="def_screen_brightness">102</integer> - <fraction name="def_window_animation_scale">0%</fraction> + <fraction name="def_window_animation_scale">100%</fraction> <fraction name="def_window_transition_scale">0%</fraction> <bool name="def_bluetooth_on">false</bool> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 2f32ab7..d39934b 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -334,7 +334,8 @@ class DatabaseHelper extends SQLiteOpenHelper { if (upgradeVersion == 31) { /* - * Animations are now turned off by default. + * Animations are now managed in preferences, and may be + * enabled or disabled based on product resources. */ db.beginTransaction(); try { diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 8814e48..f81c519 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -82,6 +82,8 @@ class MountService extends IMountService.Stub { private boolean mMounted; + private boolean mAutoStartUms; + /** * Constructs a new MountService instance * @@ -100,6 +102,8 @@ class MountService extends IMountService.Stub { mShowSafeUnmountNotificationWhenUnmounted = false; mPlaySounds = SystemProperties.get("persist.service.mount.playsnd", "1").equals("1"); + + mAutoStartUms = SystemProperties.get("persist.service.mount.umsauto", "0").equals("1"); } BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -198,6 +202,26 @@ class MountService extends IMountService.Stub { } /** + * Returns true if we auto-start UMS on cable insertion. + */ + public boolean getAutoStartUms() { + return mAutoStartUms; + } + + /** + * Set whether or not we're playing media notification sounds. + */ + public void setAutoStartUms(boolean enabled) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires WRITE_SETTINGS permission"); + } + mAutoStartUms = enabled; + SystemProperties.set("persist.service.mount.umsauto", (enabled ? "1" : "0")); + } + + /** * Update the state of the USB mass storage notification */ void updateUsbMassStorageNotification(boolean suppressIfConnected, boolean sound) { @@ -239,7 +263,14 @@ class MountService extends IMountService.Stub { !storageState.equals(Environment.MEDIA_BAD_REMOVAL) && !storageState.equals(Environment.MEDIA_CHECKING)) { - updateUsbMassStorageNotification(false, true); + if (mAutoStartUms) { + try { + setMassStorageEnabled(true); + } catch (RemoteException e) { + } + } else { + updateUsbMassStorageNotification(false, true); + } } Intent intent = new Intent(Intent.ACTION_UMS_CONNECTED); diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index fec3608..9f428e9 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -5460,6 +5460,12 @@ class PackageManagerService extends IPackageManager.Stub { // has to delete the one installed in the data partition in order to pick up the // new system package. return p; + } else if ((p.pkg != null) && (p.pkg.applicationInfo != null) && + ((p.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0)) { + // Check for non-system apps + reportSettingsProblem(Log.WARN, + "Package " + name + " codePath changed from " + p.codePath + + " to " + codePath + "; Retaining data and using new code"); } else { reportSettingsProblem(Log.WARN, "Package " + name + " codePath changed from " + p.codePath diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index 5e5fb93..a74915c 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -28,6 +28,7 @@ import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.Log; import java.util.ArrayList; import java.io.FileDescriptor; @@ -187,6 +188,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCallState(int state, String incomingNumber) { + if (!checkPhoneStatePermission("notifyCallState()")) { + return; + } synchronized (mRecords) { mCallState = state; mCallIncomingNumber = incomingNumber; @@ -206,6 +210,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyServiceState(ServiceState state) { + if (!checkPhoneStatePermission("notifyServiceState()")) { + return; + } synchronized (mRecords) { mServiceState = state; final int N = mRecords.size(); @@ -220,6 +227,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifySignalStrength(int signalStrengthASU) { + if (!checkPhoneStatePermission("notifySignalStrength()")) { + return; + } synchronized (mRecords) { mSignalStrength = signalStrengthASU; final int N = mRecords.size(); @@ -238,6 +248,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyMessageWaitingChanged(boolean mwi) { + if (!checkPhoneStatePermission("notifyMessageWaitingChanged()")) { + return; + } synchronized (mRecords) { mMessageWaiting = mwi; final int N = mRecords.size(); @@ -255,6 +268,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCallForwardingChanged(boolean cfi) { + if (!checkPhoneStatePermission("notifyCallForwardingChanged()")) { + return; + } synchronized (mRecords) { mCallForwarding = cfi; final int N = mRecords.size(); @@ -272,6 +288,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyDataActivity(int state) { + if (!checkPhoneStatePermission("notifyDataActivity()")) { + return; + } synchronized (mRecords) { mDataActivity = state; final int N = mRecords.size(); @@ -290,6 +309,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { public void notifyDataConnection(int state, boolean isDataConnectivityPissible, String reason, String apn, String interfaceName) { + if (!checkPhoneStatePermission("notifyDataConnection()")) { + return; + } synchronized (mRecords) { mDataConnectionState = state; mDataConnectionPossible = isDataConnectivityPissible; @@ -313,6 +335,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyDataConnectionFailed(String reason) { + if (!checkPhoneStatePermission("notifyDataConnectionFailed()")) { + return; + } /* * This is commented out because there is on onDataConnectionFailed callback * on PhoneStateListener. There should be. @@ -331,6 +356,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCellLocation(Bundle cellLocation) { + if (!checkPhoneStatePermission("notifyCellLocation()")) { + return; + } synchronized (mRecords) { mCellLocation = cellLocation; final int N = mRecords.size(); @@ -459,4 +487,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { intent.putExtra(Phone.FAILURE_REASON_KEY, reason); mContext.sendStickyBroadcast(intent); } + + private boolean checkPhoneStatePermission(String method) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + String msg = "Modify Phone State Permission Denial: " + method + " from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid(); + Log.w(TAG, msg); + return false; + } } diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java index adc8bc1..05e61f2 100644 --- a/telephony/java/com/android/internal/telephony/Phone.java +++ b/telephony/java/com/android/internal/telephony/Phone.java @@ -140,6 +140,7 @@ public interface Phone { static final String REASON_VOICE_CALL_STARTED = "2GVoiceCallStarted"; static final String REASON_PS_RESTRICT_ENABLED = "psRestrictEnabled"; static final String REASON_PS_RESTRICT_DISABLED = "psRestrictDisabled"; + static final String REASON_SIM_LOADED = "simLoaded"; // Used for band mode selection methods static final int BM_UNSPECIFIED = 0; // selected by baseband automatically diff --git a/telephony/java/com/android/internal/telephony/WapPushOverSms.java b/telephony/java/com/android/internal/telephony/WapPushOverSms.java index 66fa943..2b70162 100644 --- a/telephony/java/com/android/internal/telephony/WapPushOverSms.java +++ b/telephony/java/com/android/internal/telephony/WapPushOverSms.java @@ -16,11 +16,12 @@ package com.android.internal.telephony; +import android.content.Context; import android.content.Intent; +import android.os.PowerManager; 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; @@ -32,12 +33,19 @@ import com.android.internal.telephony.gsm.SimUtils; public class WapPushOverSms { private static final String LOG_TAG = "WAP PUSH"; - private final GSMPhone mPhone; + private final Context mContext; private WspTypeDecoder pduDecoder; + private PowerManager.WakeLock mWakeLock; + /** + * Hold the wake lock for 5 seconds, which should be enough time for + * any receiver(s) to grab its own wake lock. + */ + private final int WAKE_LOCK_TIMEOUT = 5000; - public WapPushOverSms(GSMPhone phone) { - mPhone = phone; + public WapPushOverSms(Phone phone) { + mContext = phone.getContext(); + createWakelock(); } /** @@ -163,8 +171,6 @@ public class WapPushOverSms { } } - - private void dispatchWapPdu_default( byte[] pdu, int transactionId, int pduType, String mimeType, int dataIndex) { byte[] data; @@ -178,8 +184,7 @@ public class WapPushOverSms { intent.putExtra("pduType", pduType); intent.putExtra("data", data); - mPhone.getContext().sendBroadcast( - intent, "android.permission.RECEIVE_WAP_PUSH"); + sendBroadcast(intent, "android.permission.RECEIVE_WAP_PUSH"); } private void dispatchWapPdu_PushCO(byte[] pdu, int transactionId, int pduType) { @@ -189,8 +194,7 @@ public class WapPushOverSms { intent.putExtra("pduType", pduType); intent.putExtra("data", pdu); - mPhone.getContext().sendBroadcast( - intent, "android.permission.RECEIVE_WAP_PUSH"); + sendBroadcast(intent, "android.permission.RECEIVE_WAP_PUSH"); } private void dispatchWapPdu_MMS(byte[] pdu, int transactionId, int pduType, int dataIndex) { @@ -205,7 +209,19 @@ public class WapPushOverSms { intent.putExtra("pduType", pduType); intent.putExtra("data", data); - mPhone.getContext().sendBroadcast( - intent, "android.permission.RECEIVE_MMS"); + sendBroadcast(intent, "android.permission.RECEIVE_MMS"); + } + + private void createWakelock() { + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WapPushOverSms"); + mWakeLock.setReferenceCounted(true); + } + + private void sendBroadcast(Intent intent, String permission) { + // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any + // receivers time to take their own wake locks. + mWakeLock.acquire(WAKE_LOCK_TIMEOUT); + mContext.sendBroadcast(intent, permission); } } diff --git a/telephony/java/com/android/internal/telephony/gsm/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/DataConnectionTracker.java index e664bc7..30b1be8 100644 --- a/telephony/java/com/android/internal/telephony/gsm/DataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/DataConnectionTracker.java @@ -199,9 +199,9 @@ final class DataConnectionTracker extends Handler private static final int POLL_NETSTAT_SLOW_MILLIS = 5000; /** Default ping deadline, in seconds. */ - private final int DEFAULT_PING_DEADLINE = 5; + private static final int DEFAULT_PING_DEADLINE = 5; /** Default max failure count before attempting to network re-registration. */ - private final int DEFAULT_MAX_PDP_RESET_FAIL = 3; + private static final int DEFAULT_MAX_PDP_RESET_FAIL = 3; /** * After detecting a potential connection problem, this is the max number @@ -677,8 +677,7 @@ final class DataConnectionTracker extends Handler if ((state == State.IDLE || state == State.SCANNING) && (gprsState == ServiceState.STATE_IN_SERVICE || noAutoAttach) && phone.mSIMRecords.getRecordsLoaded() - && ( phone.mSST.isConcurrentVoiceAndData() || - phone.getState() == Phone.State.IDLE ) + && phone.getState() == Phone.State.IDLE && isDataAllowed() && !mIsPsRestricted ) { @@ -1351,7 +1350,7 @@ final class DataConnectionTracker extends Handler if (state == State.FAILED) { cleanUpConnection(false, null); } - sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA)); + sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED)); break; case EVENT_ENABLE_NEW_APN: @@ -1362,6 +1361,10 @@ final class DataConnectionTracker extends Handler break; case EVENT_TRY_SETUP_DATA: + if (msg.obj instanceof String) { + reason = (String)msg.obj; + } + trySetupData(reason); break; @@ -1500,7 +1503,10 @@ final class DataConnectionTracker extends Handler } else { // we still have more apns to try setState(State.SCANNING); - trySetupData(reason); + // Wait a bit before trying the next APN, so that + // we're not tying up the RIL command channel + sendMessageDelayed(obtainMessage(EVENT_TRY_SETUP_DATA, reason), + RECONNECT_DELAY_INITIAL_MILLIS); } } else { startDelayedRetry(cause, reason); diff --git a/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java index 877b734..5585524 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java @@ -32,6 +32,7 @@ import android.net.Uri; import android.os.AsyncResult; import android.os.Handler; import android.os.Message; +import android.os.PowerManager; import android.provider.Telephony; import android.provider.Settings; import android.provider.Telephony.Sms.Intents; @@ -122,6 +123,15 @@ final class SMSDispatcher extends Handler { private SmsTracker mSTracker; + /** Wake lock to ensure device stays awake while dispatching the SMS intent. */ + private PowerManager.WakeLock mWakeLock; + + /** + * Hold the wake lock for 5 seconds, which should be enough time for + * any receiver(s) to grab its own wake lock. + */ + private final int WAKE_LOCK_TIMEOUT = 5000; + /** * Implement the per-application based SMS control, which only allows * a limit on the number of SMS/MMS messages an app can send in checking @@ -186,6 +196,8 @@ final class SMSDispatcher extends Handler { mCm = phone.mCM; mSTracker = null; + createWakelock(); + int check_period = Settings.Gservices.getInt(mResolver, Settings.Gservices.SMS_OUTGOING_CEHCK_INTERVAL_MS, DEFAULT_SMS_CHECK_PERIOD); @@ -286,6 +298,19 @@ final class SMSDispatcher extends Handler { } } + private void createWakelock() { + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher"); + mWakeLock.setReferenceCounted(true); + } + + private void sendBroadcast(Intent intent, String permission) { + // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any + // receivers time to take their own wake locks. + mWakeLock.acquire(WAKE_LOCK_TIMEOUT); + mContext.sendBroadcast(intent, permission); + } + /** * Called when SIM_FULL message is received from the RIL. Notifies interested * parties that SIM storage for SMS messages is full. @@ -293,7 +318,7 @@ final class SMSDispatcher extends Handler { private void handleSimFull() { // broadcast SIM_FULL intent Intent intent = new Intent(Intents.SIM_FULL_ACTION); - mPhone.getContext().sendBroadcast(intent, "android.permission.RECEIVE_SMS"); + sendBroadcast(intent, "android.permission.RECEIVE_SMS"); } /** @@ -633,8 +658,7 @@ final class SMSDispatcher extends Handler { private void dispatchPdus(byte[][] pdus) { Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION); intent.putExtra("pdus", pdus); - mPhone.getContext().sendBroadcast( - intent, "android.permission.RECEIVE_SMS"); + sendBroadcast(intent, "android.permission.RECEIVE_SMS"); } /** @@ -647,8 +671,7 @@ final class SMSDispatcher extends Handler { Uri uri = Uri.parse("sms://localhost:" + port); Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri); intent.putExtra("pdus", pdus); - mPhone.getContext().sendBroadcast( - intent, "android.permission.RECEIVE_SMS"); + sendBroadcast(intent, "android.permission.RECEIVE_SMS"); } diff --git a/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java index 7152f76..89ce506 100644 --- a/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java @@ -1511,7 +1511,7 @@ final class ServiceStateTracker extends Handler + (c.getTimeInMillis() - System.currentTimeMillis()) + " from " + nitz); - SystemClock.setCurrentTimeMillis(c.getTimeInMillis()); + setAndBroadcastNetworkSetTime(c.getTimeInMillis()); Log.i(LOG_TAG, "NITZ: after Setting time of day"); } SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis())); diff --git a/tests/CoreTests/android/webkit/CookieTest.java b/tests/CoreTests/android/webkit/CookieTest.java index 1c3d671..ea4422f 100644 --- a/tests/CoreTests/android/webkit/CookieTest.java +++ b/tests/CoreTests/android/webkit/CookieTest.java @@ -55,6 +55,11 @@ public class CookieTest extends AndroidTestCase { mCookieManager.setCookie(url, "c=\"d;\""); cookie = mCookieManager.getCookie(url); assertTrue(cookie.equals("a=b; c=\"d;\"")); + + // empty + mCookieManager.setCookie(url, "; path=/"); + cookie = mCookieManager.getCookie(url); + assertTrue(cookie.equals("a=b; c=\"d;\"")); } public void testDomain() { diff --git a/tests/DumpRenderTree/AndroidManifest.xml b/tests/DumpRenderTree/AndroidManifest.xml index 8e06cc8..17c44ad 100644 --- a/tests/DumpRenderTree/AndroidManifest.xml +++ b/tests/DumpRenderTree/AndroidManifest.xml @@ -23,7 +23,7 @@ <category android:name="android.intent.category.TEST" /> </intent-filter> </activity> - <activity android:name="HTMLHostActivity"> + <activity android:name="TestShellActivity" android:launchMode="singleTop"> </activity> </application> diff --git a/tests/DumpRenderTree/compare_layout_results.py b/tests/DumpRenderTree/compare_layout_results.py deleted file mode 100644 index c4285f1..0000000 --- a/tests/DumpRenderTree/compare_layout_results.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/python -""" -Compares results of two webkit layout test runs and writes -results to a file. -""" - -import optparse -import os -import sys - -def DiffResults(marker, new_results, old_results, diff_results, strip_reason): - """ Given two result files, generate diff and - write to diff_results file. All arguments are absolute paths - to files. - """ - old_file = open(old_results, "r") - new_file = open(new_results, "r") - diff_file = open(diff_results, "a") - - # Read lines from each file - ndict = new_file.readlines() - cdict = old_file.readlines() - - # Write marker to diff file - diff_file.writelines(marker + "\n") - diff_file.writelines("###############\n") - - # Strip reason from result lines - if strip_reason is True: - for i in range(0, len(ndict)): - ndict[i] = ndict[i].split(' ')[0] + "\n" - for i in range(0, len(cdict)): - cdict[i] = cdict[i].split(' ')[0] + "\n" - - # Find results in new_results missing in old_results - new_count=0 - for line in ndict: - if line not in cdict: - diff_file.writelines("+ " + line) - new_count += 1 - - # Find results in old_results missing in new_results - missing_count=0 - for line in cdict: - if line not in ndict: - diff_file.writelines("- " + line) - missing_count += 1 - - print marker + " >>> added " + str(new_count) + " tests, removed " + str(missing_count) + " tests" - - diff_file.writelines("\n\n") - - old_file.close() - new_file.close() - diff_file.close() - return - -def main(options, args): - results_dir = os.path.abspath(options.results_directory) - ref_dir = options.ref_directory - - # if ref_dir is null, cannonify ref_dir to the script dir. - if not ref_dir: - script_self = sys.argv[0] - script_dir = os.path.dirname(script_self) - ref_dir = os.path.join(script_dir, "results") - - ref_dir = os.path.abspath(ref_dir) - - diff_result = os.path.join(results_dir, "layout_tests_diff.txt") - if os.path.exists(diff_result): - os.remove(diff_result) - - files=["passed", "failed", "nontext", "crashed"] - for f in files: - result_file_name = "layout_tests_" + f + ".txt" - DiffResults(f, os.path.join(results_dir, result_file_name), - os.path.join(ref_dir, result_file_name), diff_result, - f == "failed") - -if '__main__' == __name__: - option_parser = optparse.OptionParser() - option_parser.add_option("", "--ref-directory", - default=None, - dest="ref_directory", - help="directory name under which results are stored.") - - option_parser.add_option("", "--results-directory", - default="layout-test-results/", - dest="results_directory", - help="directory name under which results are stored.") - options, args = option_parser.parse_args() - main(options, args) diff --git a/tests/DumpRenderTree/run_layout_tests.py b/tests/DumpRenderTree/run_layout_tests.py index 433271e..5409a0c 100755 --- a/tests/DumpRenderTree/run_layout_tests.py +++ b/tests/DumpRenderTree/run_layout_tests.py @@ -22,6 +22,7 @@ use --refresh-test-list option *once* to re-generate test list on the card. Some other options are: + --rebaseline generates expected layout tests results under /sdcard/android/expected_result/ --time-out-ms (default is 8000 millis) for each test --adb-options="-e" passes option string to adb --results-directory=..., (default is ./layout-test-results) directory name under which results are stored. @@ -55,8 +56,8 @@ def DumpRenderTreeFinished(adb_cmd): output: adb_cmd string """ - # pull /sdcard/running_test.txt, if the content is "#DONE", it's done - shell_cmd_str = adb_cmd + " shell cat /sdcard/running_test.txt" + # pull /sdcard/android/running_test.txt, if the content is "#DONE", it's done + shell_cmd_str = adb_cmd + " shell cat /sdcard/android/running_test.txt" adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] return adb_output.strip() == "#DONE" @@ -145,7 +146,7 @@ def main(options, args): # Include all tests if none are specified. if not args: - path = 'fast'; + path = '/'; else: path = ' '.join(args); @@ -156,8 +157,8 @@ def main(options, args): # Re-generate the test list if --refresh-test-list is on if options.refresh_test_list: logging.info("Generating test list."); - shell_cmd_str = adb_cmd + " shell am instrument -e class com.android.dumprendertree.LayoutTestsAutoTest#generateTestList -e path fast -w com.android.dumprendertree/.LayoutTestsAutoRunner" - adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] + generate_test_list_cmd_str = adb_cmd + " shell am instrument -e class com.android.dumprendertree.LayoutTestsAutoTest#generateTestList -e path \"" + path + "\" -w com.android.dumprendertree/.LayoutTestsAutoRunner" + adb_output = subprocess.Popen(generate_test_list_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] if adb_output.find('Process crashed') != -1: logging.info("Aborting because cannot generate test list.\n" + adb_output) @@ -169,20 +170,28 @@ def main(options, args): # Count crashed tests. crashed_tests = [] - timeout_ms = '8000' + timeout_ms = '5000' if options.time_out_ms: timeout_ms = options.time_out_ms # Run test until it's done + run_layout_test_cmd_prefix = adb_cmd + " shell am instrument" + + run_layout_test_cmd_postfix = " -e path \"" + path + "\" -e timeout " + timeout_ms + if options.rebaseline: + run_layout_test_cmd_postfix += " -e rebaseline true" + run_layout_test_cmd_postfix += " -w com.android.dumprendertree/.LayoutTestsAutoRunner" + # Call LayoutTestsAutoTest::startLayoutTests. - shell_cmd_str = adb_cmd + " shell am instrument -e class com.android.dumprendertree.LayoutTestsAutoTest#startLayoutTests -e path \"" + path + "\" -e timeout " + timeout_ms + " -w com.android.dumprendertree/.LayoutTestsAutoRunner" - adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] + run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#startLayoutTests" + run_layout_test_cmd_postfix + + adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] while not DumpRenderTreeFinished(adb_cmd): # Get the running_test.txt logging.error("DumpRenderTree crashed, output:\n" + adb_output) - shell_cmd_str = adb_cmd + " shell cat /sdcard/running_test.txt" + shell_cmd_str = adb_cmd + " shell cat /sdcard/android/running_test.txt" crashed_test = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE).communicate()[0] logging.info(crashed_test + " CRASHED"); @@ -190,9 +199,9 @@ def main(options, args): logging.info("Resuming layout test runner..."); # Call LayoutTestsAutoTest::resumeLayoutTests - shell_cmd_str = adb_cmd + " shell am instrument -e class com.android.dumprendertree.LayoutTestsAutoTest#resumeLayoutTests -e path \"" + path + "\" -e timeout " + timeout_ms + " -w com.android.dumprendertree/.LayoutTestsAutoRunner" + run_layout_test_cmd = run_layout_test_cmd_prefix + " -e class com.android.dumprendertree.LayoutTestsAutoTest#resumeLayoutTests" + run_layout_test_cmd_postfix - adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] + adb_output = subprocess.Popen(run_layout_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] if adb_output.find('INSTRUMENTATION_FAILED') != -1: logging.error("Error happened : " + adb_output) @@ -219,7 +228,7 @@ def main(options, args): # Create the crash list. fp = open(results_dir + "/layout_tests_crashed.txt", "w"); - fp.writelines(crashed_tests) + fp.writelines('\n'.join(crashed_tests)) fp.close() # Count the number of tests in each category. @@ -250,6 +259,9 @@ def main(options, args): if '__main__' == __name__: option_parser = optparse.OptionParser() + option_parser.add_option("", "--rebaseline", action="store_true", + default=False, + help="generate expected results for those tests not having one") option_parser.add_option("", "--time-out-ms", default=None, help="set the timeout for each test") diff --git a/tests/DumpRenderTree/run_page_cycler.py b/tests/DumpRenderTree/run_page_cycler.py new file mode 100755 index 0000000..9a099b5 --- /dev/null +++ b/tests/DumpRenderTree/run_page_cycler.py @@ -0,0 +1,101 @@ +#!/usr/bin/python + +"""Run page cycler tests using Android instrumentation. + + First, you need to get an SD card or sdcard image that has page cycler tests. + + Usage: + Run a single page cycler test: + run_page_cycler.py "file:///sdcard/android/page_cycler/moz/start.html?auto=1\&iterations=10" +""" + +import logging +import optparse +import os +import subprocess +import sys +import time + + + +def main(options, args): + """Run the tests. Will call sys.exit when complete. + + """ + + # Set up logging format. + log_level = logging.INFO + if options.verbose: + log_level = logging.DEBUG + logging.basicConfig(level=log_level, + format='%(message)s') + + # Include all tests if none are specified. + if not args: + print "need a URL, e.g. file:///sdcard/android/page_cycler/moz/start.html" + sys.exit(1) + else: + path = ' '.join(args); + + adb_cmd = "adb "; + if options.adb_options: + adb_cmd += options.adb_options + + logging.info("Running the test ...") + + # Count crashed tests. + crashed_tests = [] + + timeout_ms = '0' + if options.time_out_ms: + timeout_ms = options.time_out_ms + + # Run test until it's done + + run_load_test_cmd_prefix = adb_cmd + " shell am instrument" + run_load_test_cmd_postfix = " -w com.android.dumprendertree/.LayoutTestsAutoRunner" + + # Call LoadTestsAutoTest::runTest. + run_load_test_cmd = run_load_test_cmd_prefix + " -e class com.android.dumprendertree.LoadTestsAutoTest#runTest -e path \"" + path + "\" -e timeout " + timeout_ms + run_load_test_cmd_postfix + + (adb_output, adb_error) = subprocess.Popen(run_load_test_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + if adb_output.find('INSTRUMENTATION_FAILED') != -1: + logging.error("Error happened : " + adb_output) + sys.exit(1) + + logging.info(adb_output); + logging.info(adb_error); + logging.info("Done\n"); + + # Pull results from /sdcard/load_test_result.txt + results_dir = options.results_directory + if not os.path.exists(results_dir): + os.makedirs(results_dir) + if not os.path.isdir(results_dir): + logging.error("Cannot create results dir: " + results_dir) + sys.exit(1) + + result_file = "/sdcard/load_test_result.txt" + shell_cmd_str = adb_cmd + " pull " + result_file + " " + results_dir + adb_output = subprocess.Popen(shell_cmd_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] + logging.info(adb_output) + + logging.info("Results are stored under: " + results_dir + "/load_test_result.txt\n") + +if '__main__' == __name__: + option_parser = optparse.OptionParser() + option_parser.add_option("", "--time-out-ms", + default=None, + help="set the timeout for each test") + option_parser.add_option("", "--verbose", action="store_true", + default=False, + help="include debug-level logging") + option_parser.add_option("", "--adb-options", + default=None, + help="pass options to adb, such as -d -e, etc"); + option_parser.add_option("", "--results-directory", + default="layout-test-results", + help="directory which results are stored.") + + options, args = option_parser.parse_args(); + main(options, args) diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java index 9be33db..4f162b3 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java @@ -75,31 +75,14 @@ public class FileFilter { ".", // ignore hidden directories and files "resources", // ignore resource directories "AppleScript", // AppleScript not supported - "xpath", // xpath requires libxml2, not supported - "xsl", //xsl requires libxml2 & libxslt, not sup. - "kde", // don't run kde tests. ".svn", // don't run anything under .svn folder - "gradients", // known crash - "profiler" // profiler is not supported + "profiler", // profiler is not supported + "svg", // svg is not supported + "platform", // platform specific + "http" // requires local http(s) server }; static final String [] ignoreTestList = { - "toString-stack-overflow.html", // Crashes #606688 - "frame-limit.html", // generates too many GREFs - "css-insert-import-rule.html", // Crashes, #717414 - "input-text-enter.html", // Crashes. #735088 - "text-shadow-extreme-value.html", // Crashes #571671 - "reflection-masks.html", - "frame-creation-removal.html", - "large-expressions.html", - "null-page-show-modal-dialog-crash.html", - "font-face-implicit-local-font.html", - "font-face-locally-installed.html", - "beforeSelectorOnCodeElement.html", - "cssTarget-crash.html", - "searchfield-heights.html", // Bug 1570692 - "tabindex-focus-blur-all.html", - "search-rtl.html" // fast/forms/search-rtl.html }; static void fillIgnoreResultSet() { diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java index d685f5d..0218317 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileList.java @@ -17,6 +17,7 @@ package com.android.dumprendertree; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -84,7 +85,8 @@ public abstract class FileList extends ListActivity return myData; } String[] files = f.list(); - + Arrays.sort(files); + for (int i = 0; i < files.length; i++) { StringBuilder sb = new StringBuilder(mPath); sb.append(File.separatorChar); diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostActivity.java deleted file mode 100644 index 86bfad7..0000000 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostActivity.java +++ /dev/null @@ -1,726 +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.dumprendertree; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.BufferedOutputStream; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.IOException; -import java.util.List; -import java.util.Vector; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.KeyEvent; -import android.view.ViewGroup; -import android.webkit.JsPromptResult; -import android.webkit.JsResult; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.widget.LinearLayout; -import android.os.*; - -// TestRecorder creates two files, one for passing tests -// and another for failing tests and writes the paths to -// layout tests one line at a time. TestRecorder does not -// have ability to clear the results. -class TestRecorder { - public void passed(String layout_file) { - try { - mBufferedOutputPassedStream.write(layout_file.getBytes()); - mBufferedOutputPassedStream.write('\n'); - mBufferedOutputPassedStream.flush(); - } catch(Exception e) { - e.printStackTrace(); - } - } - - public void failed(String layout_file, String reason) { - try { - mBufferedOutputFailedStream.write(layout_file.getBytes()); - mBufferedOutputFailedStream.write(" : ".getBytes()); - mBufferedOutputFailedStream.write(reason.getBytes()); - mBufferedOutputFailedStream.write('\n'); - mBufferedOutputFailedStream.flush(); - } catch(Exception e) { - e.printStackTrace(); - } - } - - public void nontext(String layout_file, boolean has_results) { - try { - mBufferedOutputNontextStream.write(layout_file.getBytes()); - if (has_results) { - mBufferedOutputNontextStream.write(" : has expected results".getBytes()); - } - mBufferedOutputNontextStream.write('\n'); - mBufferedOutputNontextStream.flush(); - } catch(Exception e) { - e.printStackTrace(); - } - } - - public TestRecorder(boolean resume) { - try { - File resultsPassedFile = new File("/sdcard/layout_tests_passed.txt"); - File resultsFailedFile = new File("/sdcard/layout_tests_failed.txt"); - File resultsNontextFile = new File("/sdcard/layout_tests_nontext.txt"); - - mBufferedOutputPassedStream = - new BufferedOutputStream(new FileOutputStream(resultsPassedFile, resume)); - mBufferedOutputFailedStream = - new BufferedOutputStream(new FileOutputStream(resultsFailedFile, resume)); - mBufferedOutputNontextStream = - new BufferedOutputStream(new FileOutputStream(resultsNontextFile, resume)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void close() { - try { - mBufferedOutputPassedStream.close(); - mBufferedOutputFailedStream.close(); - mBufferedOutputNontextStream.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - private BufferedOutputStream mBufferedOutputPassedStream; - private BufferedOutputStream mBufferedOutputFailedStream; - private BufferedOutputStream mBufferedOutputNontextStream; -} - -public class HTMLHostActivity extends Activity - implements LayoutTestController { - - public class AsyncHandler extends Handler { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_TIMEOUT) { - mTimedOut = true; - requestWebKitData(); - return; - } else if (msg.what == MSG_WEBKIT_DATA) { - HTMLHostActivity.this.dump(mTimedOut, (String)msg.obj); - return; - } - - super.handleMessage(msg); - } - } - - public void requestWebKitData() { - Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA); - - if (mRequestedWebKitData) - throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl()); - - mRequestedWebKitData = true; - if (mDumpAsText) { - mWebView.documentAsText(callback); - } else { - mWebView.externalRepresentation(callback); - } - } - // Activity methods - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - LinearLayout contentView = new LinearLayout(this); - contentView.setOrientation(LinearLayout.VERTICAL); - setContentView(contentView); - - mWebView = new WebView(this); - mWebView.getSettings().setJavaScriptEnabled(true); - mWebView.setWebChromeClient(mChromeClient); - mEventSender = new WebViewEventSender(mWebView); - mCallbackProxy = new CallbackProxy(mEventSender, this); - mFinishedRunning = false; - - mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController"); - mWebView.addJavascriptInterface(mCallbackProxy, "eventSender"); - contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0f)); - - mHandler = new AsyncHandler(); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - } - - private void getTestList() { - // Read test list. - try { - BufferedReader inReader = new BufferedReader(new FileReader(LAYOUT_TESTS_LIST_FILE)); - String line = inReader.readLine(); - while (line != null) { - if (line.startsWith(mTestPathPrefix)) - mTestList.add(line); - line = inReader.readLine(); - } - inReader.close(); - Log.v(LOGTAG, "Test list has " + mTestList.size() + " test(s)."); - } catch (Exception e) { - Log.e(LOGTAG, "Error while reading test list : " + e.getMessage()); - } - } - - private void resumeTestList() { - // read out the test name it stoped last time. - try { - BufferedReader inReader = new BufferedReader(new FileReader(TEST_STATUS_FILE)); - String line = inReader.readLine(); - for (int i = 0; i < mTestList.size(); i++) { - if (mTestList.elementAt(i).equals(line)) { - mTestList = new Vector<String>(mTestList.subList(i+1, mTestList.size())); - break; - } - } - inReader.close(); - } catch (Exception e) { - Log.e(LOGTAG, "Error reading " + TEST_STATUS_FILE); - } - } - - private void clearTestStatus() { - // Delete TEST_STATUS_FILE - try { - File f = new File(TEST_STATUS_FILE); - if (f.delete()) - Log.v(LOGTAG, "Deleted " + TEST_STATUS_FILE); - else - Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE); - } catch (Exception e) { - Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE + " : " + e.getMessage()); - } - } - - private void updateTestStatus(String s) { - // Write TEST_STATUS_FILE - try { - BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TEST_STATUS_FILE)); - bos.write(s.getBytes()); - bos.close(); - } catch (Exception e) { - Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE); - } - } - - protected void onResume() { - super.onResume(); - if (mTestList == null) - mTestList = new Vector<String>(); - - if (mTestList.isEmpty()) { - // Read settings - Intent intent = getIntent(); - mTestPathPrefix = intent.getStringExtra(TEST_PATH_PREFIX); - mSingleTestMode = intent.getBooleanExtra(SINGLE_TEST_MODE, false); - boolean resume = intent.getBooleanExtra(RESUME_FROM_CRASH, false); - mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 8000); - - mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); - - if (mTestPathPrefix == null) - throw new AssertionError("mTestPathPrefix cannot be null"); - - Log.v(LOGTAG, "Run tests with prefix: " + mTestPathPrefix); - - mResultRecorder = new TestRecorder(resume); - - if (!resume) - clearTestStatus(); - - if (!mSingleTestMode) { - getTestList(); - if (resume) - resumeTestList(); - } else { - mTestList.add(mTestPathPrefix); - } - - if (!mTestList.isEmpty()) - runTestAtIndex(0); - else - mWebView.loadUrl("about:"); - } - } - - protected void onStop() { - super.onStop(); - mWebView.stopLoading(); - } - - protected void onDestroy() { - super.onDestroy(); - mResultRecorder.close(); - mWebView.destroy(); - mWebView = null; - } - - public void onLowMemory() { - super.onLowMemory(); - // Simulate a crash - Log.e(LOGTAG, "Low memory, killing self"); - System.exit(1); - } - - public boolean dispatchKeyEvent(KeyEvent event) { - // Log key strokes as they don't seem to be matched - //Log.e(LOGTAG, "Event: "+event); - return super.dispatchKeyEvent(event); - } - - // Run a test at specified index in the test list. - // Stops activity if run out of tests. - protected void runTestAtIndex(int testIndex) { - mTestIndex = testIndex; - - resetTestStatus(); - - if (testIndex == mTestList.size()) { - if (!mSingleTestMode) { - updateTestStatus("#DONE"); - } - finished(); - return; - } - String s = mTestList.elementAt(testIndex); - if (!mSingleTestMode) - updateTestStatus(s); - - Log.v(LOGTAG, " Running test: "+s); - mWebView.loadUrl("file://"+s); - - if (!mSingleTestMode) { - // Create a timeout timer - Message m = mHandler.obtainMessage(MSG_TIMEOUT); - mHandler.sendMessageDelayed(m, mTimeoutInMillis); - } - } - - // Dump the page - public void dump(boolean timeout, String webkitData) { - String currentTest = mTestList.elementAt(mTestIndex); - String resultFile = currentTest.substring(0, currentTest.lastIndexOf('.')); - - // dumpAsText version can be directly compared to expected results - if (mDumpAsText) { - resultFile += "-results.txt"; - } else { - resultFile += "-android-results.txt"; - } - - try { - FileOutputStream os = new FileOutputStream(resultFile); - if (timeout) { - Log.w("Layout test: Timeout", resultFile); - os.write(TIMEOUT_STR.getBytes()); - os.write('\n'); - } - if (mDumpTitleChanges) - os.write(mTitleChanges.toString().getBytes()); - if (mDialogStrings != null) - os.write(mDialogStrings.toString().getBytes()); - mDialogStrings = null; - os.write(webkitData.getBytes()); - os.flush(); - os.close(); - } catch (FileNotFoundException ex) { - ex.printStackTrace(); - } catch (IOException ex) { - ex.printStackTrace(); - } - - processResult(timeout, currentTest); - runTestAtIndex(mTestIndex + 1); - } - - // Wrap up - public void failedCase(String file, String reason) { - Log.w("Layout test: ", file + " failed " + reason); - mResultRecorder.failed(file, reason); - - String bugNumber = FileFilter.isKnownBug(file); - if (bugNumber != null) { - System.out.println("FAIL known:"+bugNumber+ " "+file+reason); - return; - } - if (FileFilter.ignoreResults(file)) { - return; - } - System.out.println("FAIL: "+file+reason); - } - - public void passedCase(String file) { - Log.v("Layout test:", file + " passed"); - mResultRecorder.passed(file); - - String bugNumber = FileFilter.isKnownBug(file); - if (bugNumber != null) { - System.out.println("Bug Fixed: "+bugNumber+ " "+file); - return; - } - - if (FileFilter.ignoreResults(file)) { - System.out.println("Ignored test passed: "+file); - return; - } - } - - public void nontextCase(String file, boolean has_expected_results) { - Log.v("Layout test:", file + " nontext"); - mResultRecorder.nontext(file, has_expected_results); - } - - public void setCallback(HTMLHostCallbackInterface callback) { - mCallback = callback; - } - - public void processResult(boolean timeout, String test_path) { - Log.v(LOGTAG, " Processing result: " + test_path); - // remove the extension - String short_file = test_path.substring(0, test_path.lastIndexOf('.')); - if (timeout) { - failedCase(test_path, "TIMEDOUT"); - return; - } - // Only check results that we can check, ie dumpAsText results - String dumpFile = short_file + "-results.txt"; - File f = new File(dumpFile); - if (f.exists()) { - try { - FileInputStream fr = new FileInputStream(short_file+"-results.txt"); - FileInputStream fe = new FileInputStream(short_file+"-expected.txt"); - - // If the length is different then they are different - int diff = fe.available() - fr.available(); - if (diff > 1 || diff < 0) { - failedCase(test_path, " different length"); - fr.close(); - fe.close(); - return; - } - byte[] br = new byte[fr.available()]; - byte[] be = new byte[fe.available()]; - fr.read(br); - fe.read(be); - boolean fail = false; - for (int i = 0; i < br.length; i++) { - if (br[i] != be[i]) { - failedCase(test_path, " @offset: "+i); - fr.close(); - fe.close(); - return; - } - } - if (br.length != be.length && be[be.length-1] == '\n') { - Log.d(LOGTAG, "Extra new line being ignore:" + test_path); - } - fr.close(); - fe.close(); - passedCase(test_path); - } catch (FileNotFoundException ex) { - // TODO do something here - } catch (IOException ex) { - // Failed on available() or read() - } - - return; - } - - File nontext_result = new File(short_file + "-android-results.txt"); - if (nontext_result.exists()) { - // Check if the test has expected results. - File expected = new File(short_file + "-expected.txt"); - nontextCase(test_path, expected.exists()); - } - } - - public void finished() { - if (mCallback != null) { - mCallback.waitForFinish(); - } - - mFinishedRunning = true; - finish(); - } - - // LayoutTestController Functions - public void dumpAsText() { - mDumpAsText = true; - if (mWebView != null) { - String url = mWebView.getUrl(); - Log.v(LOGTAG, "dumpAsText called: "+url); - } - } - - public void waitUntilDone() { - mWaitUntilDone = true; - String url = mWebView.getUrl(); - Log.v(LOGTAG, "waitUntilDone called: " + url); - } - public void notifyDone() { - String url = mWebView.getUrl(); - Log.v(LOGTAG, "notifyDone called: " + url); - if (mWaitUntilDone) { - mWaitUntilDone = false; - mChromeClient.onProgressChanged(mWebView, 100); - } - } - - public void display() { - mWebView.invalidate(); - } - - public void clearBackForwardList() { - mWebView.clearHistory(); - - } - - public void dumpBackForwardList() { - //printf("\n============== Back Forward List ==============\n"); - // mWebHistory - //printf("===============================================\n"); - - } - - public void dumpChildFrameScrollPositions() { - // TODO Auto-generated method stub - - } - - public void dumpEditingCallbacks() { - // TODO Auto-generated method stub - - } - - public void dumpSelectionRect() { - // TODO Auto-generated method stub - - } - - public void dumpTitleChanges() { - if (!mDumpTitleChanges) { - mTitleChanges = new StringBuffer(); - } - mDumpTitleChanges = true; - } - - public void keepWebHistory() { - if (!mKeepWebHistory) { - mWebHistory = new Vector(); - } - mKeepWebHistory = true; - } - - public void queueBackNavigation(int howfar) { - // TODO Auto-generated method stub - - } - - public void queueForwardNavigation(int howfar) { - // TODO Auto-generated method stub - - } - - public void queueLoad(String Url, String frameTarget) { - // TODO Auto-generated method stub - - } - - public void queueReload() { - mWebView.reload(); - } - - public void queueScript(String scriptToRunInCurrentContext) { - mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext); - } - - public void repaintSweepHorizontally() { - // TODO Auto-generated method stub - - } - - public void setAcceptsEditing(boolean b) { - // TODO Auto-generated method stub - - } - - public void setMainFrameIsFirstResponder(boolean b) { - // TODO Auto-generated method stub - - } - - public void setWindowIsKey(boolean b) { - // This is meant to show/hide the window. The best I can find - // is setEnabled() - mWebView.setEnabled(b); - } - - public void testRepaint() { - mWebView.invalidate(); - } - - // Instrumentation calls this to find - // if the activity has finished running the layout tests - // TODO(fqian): need to sync on mFinisheRunning - public boolean hasFinishedRunning() { - return mFinishedRunning; - } - - private final WebChromeClient mChromeClient = new WebChromeClient() { - @Override - public void onProgressChanged(WebView view, int newProgress) { - if (newProgress == 100) { - if (!mSingleTestMode && !mTimedOut && !mWaitUntilDone && !mRequestedWebKitData) { - String url = mWebView.getUrl(); - Log.v(LOGTAG, "Finished: "+ url); - mHandler.removeMessages(MSG_TIMEOUT); - requestWebKitData(); - } else { - String url = mWebView.getUrl(); - if (mSingleTestMode) { - Log.v(LOGTAG, "Single test mode: " + url); - } else if (mTimedOut) { - Log.v(LOGTAG, "Timed out before finishing: " + url); - } else if (mWaitUntilDone) { - Log.v(LOGTAG, "Waiting for notifyDone: " + url); - } else if (mRequestedWebKitData) { - Log.v(LOGTAG, "Requested webkit data ready: " + url); - } - } - } - } - - @Override - public void onReceivedTitle(WebView view, String title) { - if (title.length() > 30) - title = "..."+title.substring(title.length()-30); - setTitle(title); - if (mDumpTitleChanges) { - mTitleChanges.append("TITLE CHANGED: "); - mTitleChanges.append(title); - mTitleChanges.append("\n"); - } - } - - @Override - public boolean onJsAlert(WebView view, String url, String message, - JsResult result) { - if (mDialogStrings == null) { - mDialogStrings = new StringBuffer(); - } - mDialogStrings.append("ALERT: "); - mDialogStrings.append(message); - mDialogStrings.append('\n'); - result.confirm(); - return true; - } - - @Override - public boolean onJsConfirm(WebView view, String url, String message, - JsResult result) { - if (mDialogStrings == null) { - mDialogStrings = new StringBuffer(); - } - mDialogStrings.append("CONFIRM: "); - mDialogStrings.append(message); - mDialogStrings.append('\n'); - result.confirm(); - return true; - } - - @Override - public boolean onJsPrompt(WebView view, String url, String message, - String defaultValue, JsPromptResult result) { - if (mDialogStrings == null) { - mDialogStrings = new StringBuffer(); - } - mDialogStrings.append("PROMPT: "); - mDialogStrings.append(message); - mDialogStrings.append(", default text: "); - mDialogStrings.append(defaultValue); - mDialogStrings.append('\n'); - result.confirm(); - return true; - } - }; - - private void resetTestStatus() { - mWaitUntilDone = false; - mDumpAsText = false; - mTimedOut = false; - mDumpTitleChanges = false; - mRequestedWebKitData = false; - mEventSender.resetMouse(); - } - - private TestRecorder mResultRecorder; - private HTMLHostCallbackInterface mCallback = null; - private CallbackProxy mCallbackProxy; - - private WebView mWebView; - private WebViewEventSender mEventSender; - - private Vector<String> mTestList; - private int mTestIndex; - - private int mTimeoutInMillis; - private String mTestPathPrefix; - private boolean mSingleTestMode; - - private AsyncHandler mHandler; - private boolean mFinishedRunning; - - private boolean mTimedOut; - private boolean mRequestedWebKitData; - private boolean mDumpAsText; - private boolean mWaitUntilDone; - private boolean mDumpTitleChanges; - - private StringBuffer mTitleChanges; - private StringBuffer mDialogStrings; - - private boolean mKeepWebHistory; - private Vector mWebHistory; - - static final String TIMEOUT_STR = "**Test timeout"; - - static final int MSG_TIMEOUT = 0; - static final int MSG_WEBKIT_DATA = 1; - - static final String LOGTAG="DumpRenderTree"; - - static final String LAYOUT_TESTS_ROOT = "/sdcard/android/layout_tests/"; - static final String LAYOUT_TESTS_LIST_FILE = "/sdcard/layout_tests_list.txt"; - static final String TEST_STATUS_FILE = "/sdcard/running_test.txt"; - - static final String RESUME_FROM_CRASH = "ResumeFromCrash"; - static final String TEST_PATH_PREFIX = "TestPathPrefix"; - static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis"; - static final String SINGLE_TEST_MODE = "SingleTestMode"; -} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java index 1f37405..8f968b4 100755 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoRunner.java @@ -27,7 +27,7 @@ import android.os.Bundle; /** - * Instrumentation Test Runner for all MediaPlayer tests. + * Instrumentation Test Runner for all DumpRenderTree tests. * * Running all tests: * @@ -40,6 +40,7 @@ public class LayoutTestsAutoRunner extends InstrumentationTestRunner { public TestSuite getAllTests() { TestSuite suite = new InstrumentationTestSuite(this); suite.addTestSuite(LayoutTestsAutoTest.class); + suite.addTestSuite(LoadTestsAutoTest.class); return suite; } @@ -51,18 +52,22 @@ public class LayoutTestsAutoRunner extends InstrumentationTestRunner { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - String path = (String) icicle.get("path"); - LayoutTestsAutoTest.setLayoutTestDir(path); + this.mTestPath = (String) icicle.get("path"); String timeout_str = (String) icicle.get("timeout"); - int timeout = 0; // default value if (timeout_str != null) { try { - timeout = Integer.parseInt(timeout_str); + this.mTimeoutInMillis = Integer.parseInt(timeout_str); } catch (Exception e) { e.printStackTrace(); } } - LayoutTestsAutoTest.setTimeoutInMillis(timeout); + + String r = (String)icicle.get("rebaseline"); + this.mRebaseline = (r != null && r.toLowerCase().equals("true")); } + + public String mTestPath = null; + public int mTimeoutInMillis = 0; + public boolean mRebaseline = false; } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java index 3e65f03..a857e68 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java @@ -25,92 +25,375 @@ import android.content.Intent; import android.util.Log; import android.view.KeyEvent; +import android.webkit.WebSettings; import android.os.Bundle; import android.os.Message; -import android.test.ActivityInstrumentationTestCase; +import android.test.ActivityInstrumentationTestCase2; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; -import com.android.dumprendertree.HTMLHostActivity; +import com.android.dumprendertree.TestShellActivity; import java.io.BufferedOutputStream; +import java.io.BufferedReader; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; +import java.util.Vector; + +//TestRecorder creates two files, one for passing tests +//and another for failing tests and writes the paths to +//layout tests one line at a time. TestRecorder does not +//have ability to clear the results. +class MyTestRecorder { + private BufferedOutputStream mBufferedOutputPassedStream; + private BufferedOutputStream mBufferedOutputFailedStream; + private BufferedOutputStream mBufferedOutputNoresultStream; + + public void passed(String layout_file) { + try { + mBufferedOutputPassedStream.write(layout_file.getBytes()); + mBufferedOutputPassedStream.write('\n'); + mBufferedOutputPassedStream.flush(); + } catch(Exception e) { + e.printStackTrace(); + } + } + + public void failed(String layout_file) { + try { + mBufferedOutputFailedStream.write(layout_file.getBytes()); + mBufferedOutputFailedStream.write('\n'); + mBufferedOutputFailedStream.flush(); + } catch(Exception e) { + e.printStackTrace(); + } + } + + public void noresult(String layout_file) { + try { + mBufferedOutputNoresultStream.write(layout_file.getBytes()); + mBufferedOutputNoresultStream.write('\n'); + mBufferedOutputNoresultStream.flush(); + } catch(Exception e) { + e.printStackTrace(); + } + } + + public MyTestRecorder(boolean resume) { + try { + File resultsPassedFile = new File("/sdcard/layout_tests_passed.txt"); + File resultsFailedFile = new File("/sdcard/layout_tests_failed.txt"); + File noExpectedResultFile = new File("/sdcard/layout_tests_nontext.txt"); + + mBufferedOutputPassedStream = + new BufferedOutputStream(new FileOutputStream(resultsPassedFile, resume)); + mBufferedOutputFailedStream = + new BufferedOutputStream(new FileOutputStream(resultsFailedFile, resume)); + mBufferedOutputNoresultStream = + new BufferedOutputStream(new FileOutputStream(noExpectedResultFile, resume)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void close() { + try { + mBufferedOutputPassedStream.close(); + mBufferedOutputFailedStream.close(); + mBufferedOutputNoresultStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} -public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase<Menu> { - private final static String LOGTAG = "LayoutTests"; - private final static int DEFAULT_TIMEOUT_IN_MILLIS = 6000; - private static String layoutTestDir = null; - private static int mTimeoutInMillis = 0; +public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestShellActivity> { + + private static final String LOGTAG = "LayoutTests"; + static final int DEFAULT_TIMEOUT_IN_MILLIS = 5000; + + static final String LAYOUT_TESTS_ROOT = "/sdcard/android/layout_tests/"; + static final String LAYOUT_TESTS_RESULT_DIR = "/sdcard/android/layout_tests_results/"; + static final String ANDROID_EXPECTED_RESULT_DIR = "/sdcard/android/expected_results/"; + static final String LAYOUT_TESTS_LIST_FILE = "/sdcard/android/layout_tests_list.txt"; + static final String TEST_STATUS_FILE = "/sdcard/android/running_test.txt"; + + private MyTestRecorder mResultRecorder; + private Vector<String> mTestList; + private boolean mRebaselineResults; + private String mTestPathPrefix; public LayoutTestsAutoTest() { - super("com.android.dumprendertree", Menu.class); + super("com.android.dumprendertree", TestShellActivity.class); } // This function writes the result of the layout test to // Am status so that it can be picked up from a script. - public void passOrFailCallback(String file, boolean result) { + private void passOrFailCallback(String file, boolean result) { Instrumentation inst = getInstrumentation(); Bundle bundle = new Bundle(); bundle.putBoolean(file, result); inst.sendStatus(0, bundle); } + + private void getTestList() { + // Read test list. + try { + BufferedReader inReader = new BufferedReader(new FileReader(LAYOUT_TESTS_LIST_FILE)); + String line = inReader.readLine(); + while (line != null) { + if (line.startsWith(mTestPathPrefix)) + mTestList.add(line); + line = inReader.readLine(); + } + inReader.close(); + Log.v(LOGTAG, "Test list has " + mTestList.size() + " test(s)."); + } catch (Exception e) { + Log.e(LOGTAG, "Error while reading test list : " + e.getMessage()); + } + } + + private void resumeTestList() { + // read out the test name it stoped last time. + try { + BufferedReader inReader = new BufferedReader(new FileReader(TEST_STATUS_FILE)); + String line = inReader.readLine(); + for (int i = 0; i < mTestList.size(); i++) { + if (mTestList.elementAt(i).equals(line)) { + mTestList = new Vector<String>(mTestList.subList(i+1, mTestList.size())); + break; + } + } + inReader.close(); + } catch (Exception e) { + Log.e(LOGTAG, "Error reading " + TEST_STATUS_FILE); + } + } + + private void clearTestStatus() { + // Delete TEST_STATUS_FILE + try { + File f = new File(TEST_STATUS_FILE); + if (f.delete()) + Log.v(LOGTAG, "Deleted " + TEST_STATUS_FILE); + else + Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE); + } catch (Exception e) { + Log.e(LOGTAG, "Fail to delete " + TEST_STATUS_FILE + " : " + e.getMessage()); + } + } + + private void updateTestStatus(String s) { + // Write TEST_STATUS_FILE + try { + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TEST_STATUS_FILE)); + bos.write(s.getBytes()); + bos.close(); + } catch (Exception e) { + Log.e(LOGTAG, "Cannot update file " + TEST_STATUS_FILE); + } + } + + private String getResultFile(String test) { + String shortName = test.substring(0, test.lastIndexOf('.')); + // Write actual results to result directory. + return shortName.replaceFirst(LAYOUT_TESTS_ROOT, LAYOUT_TESTS_RESULT_DIR) + "-result.txt"; + } + + private String getExpectedResultFile(String test) { + String shortName = test.substring(0, test.lastIndexOf('.')); + return shortName + "-expected.txt"; + } + + private String getAndroidExpectedResultFile(String expectedResultFile) { + return expectedResultFile.replaceFirst(LAYOUT_TESTS_ROOT, ANDROID_EXPECTED_RESULT_DIR); + } + + // Wrap up + private void failedCase(String file) { + Log.w("Layout test: ", file + " failed"); + mResultRecorder.failed(file); + } - public static void setTimeoutInMillis(int millis) { - mTimeoutInMillis = (millis > 0) ? millis : DEFAULT_TIMEOUT_IN_MILLIS; + private void passedCase(String file) { + Log.v("Layout test:", file + " passed"); + mResultRecorder.passed(file); } - public static void setLayoutTestDir(String name) { - if (name == null) - throw new AssertionError("Layout test directory cannot be null."); - layoutTestDir = HTMLHostActivity.LAYOUT_TESTS_ROOT + name; - Log.v("LayoutTestsAutoTest", " Only running the layout tests : " + layoutTestDir); + private void noresultCase(String file) { + Log.v("Layout test:", file + " no expected result"); + mResultRecorder.noresult(file); } + + private void processResult(String testFile, String actualResultFile, String expectedResultFile) { + Log.v(LOGTAG, " Processing result: " + testFile); + File actual = new File(actualResultFile); + File expected = new File(expectedResultFile); + if (actual.exists() && expected.exists()) { + try { + boolean passing = true; + BufferedReader fr = new BufferedReader(new FileReader(actual)); + BufferedReader fe = new BufferedReader(new FileReader(expected)); + while (true) { + String s1 = fr.readLine(); + String s2 = fe.readLine(); + if (s1 == null && s2 == null) + break; // both files are the same + if (s1 == null || s2 == null || !s1.equals(s2)) { + passing = false; + break; + } + } + + if (passing) { + passedCase(testFile); + } else { + failedCase(testFile); + } + + fe.close(); + fr.close(); + } catch (FileNotFoundException ex) { + Log.e(LOGTAG, "File not found : " + ex.getMessage()); + } catch (IOException ex) { + Log.e(LOGTAG, "IO Error : " + ex.getMessage()); + } + return; + } + + if (!expected.exists()) { + noresultCase(testFile); + } + } + + private void runTestAndWaitUntilDone(TestShellActivity activity, String test, int timeout) { + activity.setCallback(new TestShellCallback() { + public void finished() { + synchronized (LayoutTestsAutoTest.this) { + LayoutTestsAutoTest.this.notifyAll(); + } + } + }); + + String resultFile = getResultFile(test); + if (mRebaselineResults) { + String expectedResultFile = getExpectedResultFile(test); + File f = new File(expectedResultFile); + if (f.exists()) { + return; // don't run test and don't overwrite default tests. + } + + resultFile = getAndroidExpectedResultFile(expectedResultFile); + } + + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setClass(activity, TestShellActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(TestShellActivity.TEST_URL, "file://" + test); + intent.putExtra(TestShellActivity.RESULT_FILE, resultFile); + intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout); + activity.startActivity(intent); + + // Wait until done. + synchronized (this) { + try { + this.wait(); + } catch (InterruptedException e) { } + } + + if (!mRebaselineResults) { + String expectedResultFile = getExpectedResultFile(test); + File f = new File(expectedResultFile); + if (!f.exists()) { + expectedResultFile = getAndroidExpectedResultFile(expectedResultFile); + } + + processResult(test, resultFile, expectedResultFile); + } + } + // Invokes running of layout tests // and waits till it has finished running. public void executeLayoutTests(boolean resume) { - Instrumentation inst = getInstrumentation(); - - { - Activity activity = getActivity(); - Intent intent = new Intent(); - intent.setClass(activity, HTMLHostActivity.class); - intent.putExtra(HTMLHostActivity.RESUME_FROM_CRASH, resume); - intent.putExtra(HTMLHostActivity.SINGLE_TEST_MODE, false); - intent.putExtra(HTMLHostActivity.TEST_PATH_PREFIX, layoutTestDir); - intent.putExtra(HTMLHostActivity.TIMEOUT_IN_MILLIS, mTimeoutInMillis); - activity.startActivity(intent); - } + LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation(); + // A convenient method to be called by another activity. + + if (runner.mTestPath == null) { + Log.e(LOGTAG, "No test specified"); + return; + } + + this.mTestList = new Vector<String>(); - ActivityMonitor htmlHostActivityMonitor = - inst.addMonitor("com.android.dumprendertree.HTMLHostActivity", null, false); + // Read settings + try { + this.mTestPathPrefix = + (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getCanonicalPath(); + } catch (IOException e) { + Log.e(LOGTAG, "Cannot find test path prefix: " + e.getMessage()); + return; + } + + this.mRebaselineResults = runner.mRebaseline; + + int timeout = runner.mTimeoutInMillis; + if (timeout <= 0) { + timeout = DEFAULT_TIMEOUT_IN_MILLIS; + } + + this.mResultRecorder = new MyTestRecorder(resume); + + if (!resume) + clearTestStatus(); + + getTestList(); + if (resume) + resumeTestList(); + + TestShellActivity activity = (TestShellActivity) getActivity(); - HTMLHostActivity activity = - (HTMLHostActivity) htmlHostActivityMonitor.waitForActivity(); + // Run tests. + for (int i = 0; i < mTestList.size(); i++) { + String s = mTestList.elementAt(i); + updateTestStatus(s); + // Run tests + runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis); + } - while (!activity.hasFinishedRunning()) { - // Poll every 5 seconds to determine if the layout - // tests have finished running - try {Thread.sleep(5000); } catch(Exception e){} - } + updateTestStatus("#DONE"); + + activity.finish(); + } - // Wait few more seconds so that results are - // flushed to the /sdcard - try {Thread.sleep(5000); } catch(Exception e){} - // Clean up the HTMLHostActivity activity - activity.finish(); + private String getTestPath() { + LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation(); + + String test_path = LAYOUT_TESTS_ROOT; + if (runner.mTestPath != null) { + test_path += runner.mTestPath; + } + try { + test_path = new File(test_path).getCanonicalPath(); + } catch (IOException e) { + Log.e("LayoutTestsAutoTest", "Cannot get cannonical path " + e.getMessage()); + } + Log.v("LayoutTestsAutoTest", " Test path : " + test_path); + + return test_path; } public void generateTestList() { try { - File tests_list = new File(HTMLHostActivity.LAYOUT_TESTS_LIST_FILE); + File tests_list = new File(LAYOUT_TESTS_LIST_FILE); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false)); - findTestsRecursively(bos, layoutTestDir); + findTestsRecursively(bos, getTestPath()); bos.flush(); bos.close(); } catch (Exception e) { @@ -156,7 +439,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase<Menu> { // in chunks. public void startLayoutTests() { try { - File tests_list = new File(HTMLHostActivity.LAYOUT_TESTS_LIST_FILE); + File tests_list = new File(LAYOUT_TESTS_LIST_FILE); if (!tests_list.exists()) generateTestList(); } catch (Exception e) { diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java new file mode 100644 index 0000000..b064dbb --- /dev/null +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LoadTestsAutoTest.java @@ -0,0 +1,152 @@ +/* + * 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.dumprendertree; + +import android.app.Activity; +import android.app.Instrumentation; +import android.app.Instrumentation.ActivityMonitor; +import android.content.Intent; + +import android.util.Log; + +import android.os.Bundle; +import android.test.ActivityInstrumentationTestCase2; + +import com.android.dumprendertree.TestShellActivity; +import com.android.dumprendertree.TestShellCallback; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +class StreamPipe extends Thread { + InputStream in; + OutputStream out; + + StreamPipe(InputStream in, OutputStream out) { + this.in = in; + this.out = out; + } + + public void run() { + try { + byte[] buf = new byte[1024]; + int nofb = this.in.read(buf); + while (nofb != -1) { + this.out.write(buf, 0, nofb); + nofb = this.in.read(buf); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} + +public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShellActivity> { + + private final static String LOGTAG = "LoadTest"; + private final static String LOAD_TEST_RESULT = "/sdcard/load_test_result.txt"; + + public LoadTestsAutoTest() { + super("com.android.dumprendertree", TestShellActivity.class); + } + + // This function writes the result of the layout test to + // Am status so that it can be picked up from a script. + public void passOrFailCallback(String file, boolean result) { + Instrumentation inst = getInstrumentation(); + Bundle bundle = new Bundle(); + bundle.putBoolean(file, result); + inst.sendStatus(0, bundle); + } + + // Invokes running of layout tests + // and waits till it has finished running. + public void runTest() { + LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation(); + + if (runner.mTestPath == null) { + Log.e(LOGTAG, "No test specified"); + return; + } + + TestShellActivity activity = (TestShellActivity) getActivity(); + + // Run tests + runTestAndWaitUntilDone(activity, runner.mTestPath, runner.mTimeoutInMillis); + + // TODO(fqian): let am instrumentation pass in the command line, currently + // am instrument does not allow spaces in the command. + runPostShellCommand("/system/bin/dumpsys meminfo"); + + // Kill activity + activity.finish(); + } + + private void runPostShellCommand(String cmd) { + if (cmd == null || cmd.length() == 0) + return; + + try { + // Call dumpsys meminfo + Process proc = Runtime.getRuntime().exec(cmd); + // Append output to LOAD_TEST_RESULT + InputStream input = proc.getInputStream(); + InputStream error = proc.getErrorStream(); + FileOutputStream out = new FileOutputStream(LOAD_TEST_RESULT, true); + + StreamPipe p_in = new StreamPipe(input, out); + StreamPipe p_err = new StreamPipe(error, System.err); + + p_in.start(); + p_err.start(); + + proc.waitFor(); + } catch (IOException e) { + Log.e(LOGTAG, e.getMessage()); + } catch (InterruptedException e) { + Log.e(LOGTAG, e.getMessage()); + } + } + + // A convenient method to be called by another activity. + private void runTestAndWaitUntilDone(TestShellActivity activity, String url, int timeout) { + activity.setCallback(new TestShellCallback() { + public void finished() { + synchronized (LoadTestsAutoTest.this) { + LoadTestsAutoTest.this.notifyAll(); + } + } + }); + + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setClass(activity, TestShellActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(TestShellActivity.TEST_URL, url); + intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout); + intent.putExtra(TestShellActivity.RESULT_FILE, LOAD_TEST_RESULT); + activity.startActivity(intent); + + // Wait until done. + synchronized (this) { + try { + this.wait(); + } catch (InterruptedException e) { } + } + } +} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java b/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java index de0da61..00e0f89 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java @@ -19,6 +19,7 @@ package com.android.dumprendertree; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.util.Log; import java.io.File; @@ -42,15 +43,12 @@ public class Menu extends FileList { } void processFile(String filename, boolean selection) - { - Intent result = new Intent(); - result.setClass(this, HTMLHostActivity.class); - result.putExtra(HTMLHostActivity.RESUME_FROM_CRASH, false); - result.putExtra(HTMLHostActivity.SINGLE_TEST_MODE, true); - result.putExtra(HTMLHostActivity.TEST_PATH_PREFIX, filename); - result.putExtra(HTMLHostActivity.TIMEOUT_IN_MILLIS, 8000); - startActivity(result); + { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setClass(this, TestShellActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(TestShellActivity.TEST_URL, "file://" + filename); + startActivity(intent); } - } diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java new file mode 100644 index 0000000..bf8a3b3 --- /dev/null +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java @@ -0,0 +1,426 @@ +/* + * 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.dumprendertree; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Vector; + +import android.app.Activity; +import android.content.Intent; +import android.util.Log; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; +import android.view.ViewGroup; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.widget.LinearLayout; +import android.os.*; + +public class TestShellActivity extends Activity implements LayoutTestController { + public class AsyncHandler extends Handler { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_TIMEOUT) { + mTimedOut = true; + requestWebKitData(); + return; + } else if (msg.what == MSG_WEBKIT_DATA) { + TestShellActivity.this.dump(mTimedOut, (String)msg.obj); + return; + } + + super.handleMessage(msg); + } + } + + public void requestWebKitData() { + Message callback = mHandler.obtainMessage(MSG_WEBKIT_DATA); + + if (mRequestedWebKitData) + throw new AssertionError("Requested webkit data twice: " + mWebView.getUrl()); + + mRequestedWebKitData = true; + if (mDumpAsText) { + mWebView.documentAsText(callback); + } else { + mWebView.externalRepresentation(callback); + } + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + LinearLayout contentView = new LinearLayout(this); + contentView.setOrientation(LinearLayout.VERTICAL); + setContentView(contentView); + + mWebView = new WebView(this); + mWebView.getSettings().setJavaScriptEnabled(true); + mWebView.setWebChromeClient(mChromeClient); + mEventSender = new WebViewEventSender(mWebView); + mCallbackProxy = new CallbackProxy(mEventSender, this); + + mWebView.addJavascriptInterface(mCallbackProxy, "layoutTestController"); + mWebView.addJavascriptInterface(mCallbackProxy, "eventSender"); + contentView.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT, 0.0f)); + + mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); + + mHandler = new AsyncHandler(); + + Intent intent = getIntent(); + if (intent != null) { + executeIntent(intent); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + executeIntent(intent); + } + + private void executeIntent(Intent intent) { + resetTestStatus(); + if (!Intent.ACTION_VIEW.equals(intent.getAction())) { + return; + } + + mTestUrl = intent.getStringExtra(TEST_URL); + if (mTestUrl == null) + return; + + mResultFile = intent.getStringExtra(RESULT_FILE); + mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0); + + Log.v(LOGTAG, " Loading " + mTestUrl); + mWebView.loadUrl(mTestUrl); + + if (mTimeoutInMillis > 0) { + // Create a timeout timer + Message m = mHandler.obtainMessage(MSG_TIMEOUT); + mHandler.sendMessageDelayed(m, mTimeoutInMillis); + } + } + + @Override + protected void onStop() { + super.onStop(); + mWebView.stopLoading(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mWebView.destroy(); + mWebView = null; + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + Log.e(LOGTAG, "Low memory, kill self"); + System.exit(1); + } + + // Dump the page + public void dump(boolean timeout, String webkitData) { + if (mResultFile == null || mResultFile.length() == 0) { + finished(); + return; + } + + try { + File parentDir = new File(mResultFile).getParentFile(); + if (!parentDir.exists()) { + parentDir.mkdirs(); + } + + FileOutputStream os = new FileOutputStream(mResultFile); + if (timeout) { + Log.w("Layout test: Timeout", mResultFile); + os.write(TIMEOUT_STR.getBytes()); + os.write('\n'); + } + if (mDumpTitleChanges) + os.write(mTitleChanges.toString().getBytes()); + if (mDialogStrings != null) + os.write(mDialogStrings.toString().getBytes()); + mDialogStrings = null; + os.write(webkitData.getBytes()); + os.flush(); + os.close(); + } catch (IOException ex) { + Log.e(LOGTAG, "Cannot write to " + mResultFile + ", " + ex.getMessage()); + } + + finished(); + } + + public void setCallback(TestShellCallback callback) { + mCallback = callback; + } + + public void finished() { + if (mCallback != null) { + mCallback.finished(); + } + } + + // ....................................... + // LayoutTestController Functions + public void dumpAsText() { + mDumpAsText = true; + if (mWebView != null) { + String url = mWebView.getUrl(); + Log.v(LOGTAG, "dumpAsText called: "+url); + } + } + + public void waitUntilDone() { + mWaitUntilDone = true; + String url = mWebView.getUrl(); + Log.v(LOGTAG, "waitUntilDone called: " + url); + } + + public void notifyDone() { + String url = mWebView.getUrl(); + Log.v(LOGTAG, "notifyDone called: " + url); + if (mWaitUntilDone) { + mWaitUntilDone = false; + mChromeClient.onProgressChanged(mWebView, 100); + } + } + + public void display() { + mWebView.invalidate(); + } + + public void clearBackForwardList() { + mWebView.clearHistory(); + + } + + public void dumpBackForwardList() { + //printf("\n============== Back Forward List ==============\n"); + // mWebHistory + //printf("===============================================\n"); + + } + + public void dumpChildFrameScrollPositions() { + // TODO Auto-generated method stub + + } + + public void dumpEditingCallbacks() { + // TODO Auto-generated method stub + + } + + public void dumpSelectionRect() { + // TODO Auto-generated method stub + + } + + public void dumpTitleChanges() { + if (!mDumpTitleChanges) { + mTitleChanges = new StringBuffer(); + } + mDumpTitleChanges = true; + } + + public void keepWebHistory() { + if (!mKeepWebHistory) { + mWebHistory = new Vector(); + } + mKeepWebHistory = true; + } + + public void queueBackNavigation(int howfar) { + // TODO Auto-generated method stub + + } + + public void queueForwardNavigation(int howfar) { + // TODO Auto-generated method stub + + } + + public void queueLoad(String Url, String frameTarget) { + // TODO Auto-generated method stub + + } + + public void queueReload() { + mWebView.reload(); + } + + public void queueScript(String scriptToRunInCurrentContext) { + mWebView.loadUrl("javascript:"+scriptToRunInCurrentContext); + } + + public void repaintSweepHorizontally() { + // TODO Auto-generated method stub + + } + + public void setAcceptsEditing(boolean b) { + // TODO Auto-generated method stub + + } + + public void setMainFrameIsFirstResponder(boolean b) { + // TODO Auto-generated method stub + + } + + public void setWindowIsKey(boolean b) { + // This is meant to show/hide the window. The best I can find + // is setEnabled() + mWebView.setEnabled(b); + } + + public void testRepaint() { + mWebView.invalidate(); + } + + private final WebChromeClient mChromeClient = new WebChromeClient() { + @Override + public void onProgressChanged(WebView view, int newProgress) { + if (newProgress == 100) { + if (!mTimedOut && !mWaitUntilDone && !mRequestedWebKitData) { + String url = mWebView.getUrl(); + Log.v(LOGTAG, "Finished: "+ url); + mHandler.removeMessages(MSG_TIMEOUT); + requestWebKitData(); + } else { + String url = mWebView.getUrl(); + if (mTimedOut) { + Log.v(LOGTAG, "Timed out before finishing: " + url); + } else if (mWaitUntilDone) { + Log.v(LOGTAG, "Waiting for notifyDone: " + url); + } else if (mRequestedWebKitData) { + Log.v(LOGTAG, "Requested webkit data ready: " + url); + } + } + } + } + + @Override + public void onReceivedTitle(WebView view, String title) { + if (title.length() > 30) + title = "..."+title.substring(title.length()-30); + setTitle(title); + if (mDumpTitleChanges) { + mTitleChanges.append("TITLE CHANGED: "); + mTitleChanges.append(title); + mTitleChanges.append("\n"); + } + } + + @Override + public boolean onJsAlert(WebView view, String url, String message, + JsResult result) { + if (mDialogStrings == null) { + mDialogStrings = new StringBuffer(); + } + mDialogStrings.append("ALERT: "); + mDialogStrings.append(message); + mDialogStrings.append('\n'); + result.confirm(); + return true; + } + + @Override + public boolean onJsConfirm(WebView view, String url, String message, + JsResult result) { + if (mDialogStrings == null) { + mDialogStrings = new StringBuffer(); + } + mDialogStrings.append("CONFIRM: "); + mDialogStrings.append(message); + mDialogStrings.append('\n'); + result.confirm(); + return true; + } + + @Override + public boolean onJsPrompt(WebView view, String url, String message, + String defaultValue, JsPromptResult result) { + if (mDialogStrings == null) { + mDialogStrings = new StringBuffer(); + } + mDialogStrings.append("PROMPT: "); + mDialogStrings.append(message); + mDialogStrings.append(", default text: "); + mDialogStrings.append(defaultValue); + mDialogStrings.append('\n'); + result.confirm(); + return true; + } + }; + + private void resetTestStatus() { + mWaitUntilDone = false; + mDumpAsText = false; + mTimedOut = false; + mDumpTitleChanges = false; + mRequestedWebKitData = false; + mEventSender.resetMouse(); + } + + private WebView mWebView; + private WebViewEventSender mEventSender; + private AsyncHandler mHandler; + private TestShellCallback mCallback; + + private CallbackProxy mCallbackProxy; + + private String mTestUrl; + private String mResultFile; + private int mTimeoutInMillis; + + // States + private boolean mTimedOut; + private boolean mRequestedWebKitData; + private boolean mFinishedRunning; + + // Layout test controller variables. + private boolean mDumpAsText; + private boolean mWaitUntilDone; + private boolean mDumpTitleChanges; + private StringBuffer mTitleChanges; + private StringBuffer mDialogStrings; + private boolean mKeepWebHistory; + private Vector mWebHistory; + + static final String TIMEOUT_STR = "**Test timeout"; + + static final int MSG_TIMEOUT = 0; + static final int MSG_WEBKIT_DATA = 1; + + static final String LOGTAG="TestShell"; + + static final String TEST_URL = "TestUrl"; + static final String RESULT_FILE = "ResultFile"; + static final String TIMEOUT_IN_MILLIS = "TimeoutInMillis"; +} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostCallbackInterface.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellCallback.java index 60a2915..759c443 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/HTMLHostCallbackInterface.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellCallback.java @@ -16,6 +16,6 @@ package com.android.dumprendertree; -public interface HTMLHostCallbackInterface { - public void waitForFinish(); +public interface TestShellCallback { + public void finished(); } diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp index e248763..fc658f5 100644 --- a/tools/aidl/aidl.cpp +++ b/tools/aidl/aidl.cpp @@ -610,6 +610,62 @@ generate_dep_file(const Options& options) } // ========================================================== +static string +generate_outputFileName(const Options& options, const document_item_type* items) +{ + string result; + + // items has already been checked to have only one interface. + if (items->item_type == INTERFACE_TYPE) { + interface_type* type = (interface_type*)items; + + // create the path to the destination folder based on the + // interface package name + result = options.outputBaseFolder; + result += OS_PATH_SEPARATOR; + + string package = type->package; + size_t len = package.length(); + for (size_t i=0; i<len; i++) { + if (package[i] == '.') { + package[i] = OS_PATH_SEPARATOR; + } + } + + result += package; + + // add the filename by replacing the .aidl extension to .java + const char* p = strchr(type->name.data, '.'); + len = p ? p-type->name.data : strlen(type->name.data); + + result += OS_PATH_SEPARATOR; + result.append(type->name.data, len); + result += ".java"; + } + + return result; +} + +// ========================================================== +static void +check_outputFileName(const string& path) { + size_t len = path.length(); + for (size_t i=0; i<len ; i++) { + if (path[i] == OS_PATH_SEPARATOR) { + string p = path.substr(0, i); + if (access(path.data(), F_OK) != 0) { +#ifdef HAVE_MS_C_RUNTIME + _mkdir(p.data()); +#else + mkdir(p.data(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); +#endif + } + } + } +} + + +// ========================================================== static int parse_preprocessed_file(const string& filename) { @@ -804,7 +860,17 @@ compile_aidl(const Options& options) generate_dep_file(options); } - err = generate_java(options.outputFileName, options.inputFileName.c_str(), + // if needed, generate the outputFileName from the outputBaseFolder + string outputFileName = options.outputFileName; + if (outputFileName.length() == 0 && + options.outputBaseFolder.length() > 0) { + outputFileName = generate_outputFileName(options, mainDoc); + } + + // make sure the folders of the output file all exists + check_outputFileName(outputFileName); + + err = generate_java(outputFileName, options.inputFileName.c_str(), (interface_type*)mainDoc); return err; diff --git a/tools/aidl/options.cpp b/tools/aidl/options.cpp index 57b10ae..0aa7db2 100644 --- a/tools/aidl/options.cpp +++ b/tools/aidl/options.cpp @@ -13,16 +13,19 @@ usage() " aidl --preprocess OUTPUT INPUT...\n" "\n" "OPTIONS:\n" - " -I<DIR> search path for import statements.\n" - " -d<FILE> generate dependency file.\n" - " -p<FILE> file created by --preprocess to import.\n" - " -b fail when trying to compile a parcelable.\n" + " -I<DIR> search path for import statements.\n" + " -d<FILE> generate dependency file.\n" + " -p<FILE> file created by --preprocess to import.\n" + " -o<FOLDER> base output folder for generated files.\n" + " -b fail when trying to compile a parcelable.\n" "\n" "INPUT:\n" " An aidl interface file.\n" "\n" "OUTPUT:\n" - " The generated interface files. If omitted, the input filename is used, with the .aidl extension changed to a .java extension.\n" + " The generated interface files.\n" + " If omitted and the -o option is not used, the input filename is used, with the .aidl extension changed to a .java extension.\n" + " If the -o option is used, the generated files will be placed in the base output folder, under their package folder\n" ); return 1; } @@ -78,6 +81,14 @@ parse_options(int argc, const char* const* argv, Options *options) return usage(); } } + else if (s[1] == 'o') { + if (len > 2) { + options->outputBaseFolder = s+2; + } else { + fprintf(stderr, "-o option (%d) requires a path.\n", i); + return usage(); + } + } else if (len == 2 && s[1] == 'b') { options->failOnParcelable = true; } @@ -111,7 +122,7 @@ parse_options(int argc, const char* const* argv, Options *options) if (i < argc) { options->outputFileName = argv[i]; i++; - } else { + } else if (options->outputBaseFolder.length() == 0) { // copy input into output and change the extension from .aidl to .java options->outputFileName = options->inputFileName; string::size_type pos = options->outputFileName.size()-5; diff --git a/tools/aidl/options.h b/tools/aidl/options.h index dc3c45a..e9bf0f7 100644 --- a/tools/aidl/options.h +++ b/tools/aidl/options.h @@ -20,6 +20,7 @@ struct Options vector<string> preprocessedFiles; string inputFileName; string outputFileName; + string outputBaseFolder; string depFileName; vector<string> filesToPreprocess; |